Skip to content

Copy globals from program memory into RAM #71

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dylanmckay opened this issue Aug 24, 2017 · 20 comments
Closed

Copy globals from program memory into RAM #71

dylanmckay opened this issue Aug 24, 2017 · 20 comments

Comments

@dylanmckay
Copy link
Member

dylanmckay commented Aug 24, 2017

It looks like avr-gcc generates a __do_copy_data function to do this.

An example program.

int foo = 3;

int main() {
  volatile unsigned char* PORTB = 0x25;
  *PORTB = foo;

  return 0;
}

Disassembly

Disassembly of section .text:

00000000 <__do_copy_data>:
   0:   10 e0           ldi     r17, 0x00       ; 0
   2:   a0 e6           ldi     r26, 0x60       ; 96
   4:   b0 e0           ldi     r27, 0x00       ; 0
   6:   ec e4           ldi     r30, 0x4C       ; 76
   8:   f0 e0           ldi     r31, 0x00       ; 0
   a:   03 c0           rjmp    .+6             ; 0x12 <__zero_reg__+0x11>
   c:   c8 95           lpm
   e:   31 96           adiw    r30, 0x01       ; 1
  10:   0d 92           st      X+, r0
  12:   a2 36           cpi     r26, 0x62       ; 98
  14:   b1 07           cpc     r27, r17
  16:   d1 f7           brne    .-12            ; 0xc <__zero_reg__+0xb>

00000018 <main>:
  18:   cf 93           push    r28
  1a:   df 93           push    r29
  1c:   00 d0           rcall   .+0             ; 0x1e <main+0x6>
  1e:   cd b7           in      r28, 0x3d       ; 61
  20:   de b7           in      r29, 0x3e       ; 62
  22:   85 e2           ldi     r24, 0x25       ; 37
  24:   90 e0           ldi     r25, 0x00       ; 0
  26:   9a 83           std     Y+2, r25        ; 0x02
  28:   89 83           std     Y+1, r24        ; 0x01
  2a:   80 91 60 00     lds     r24, 0x0060     ; 0x800060 <foo>
  2e:   90 91 61 00     lds     r25, 0x0061     ; 0x800061 <foo+0x1>
  32:   28 2f           mov     r18, r24
  34:   89 81           ldd     r24, Y+1        ; 0x01
  36:   9a 81           ldd     r25, Y+2        ; 0x02
  38:   e8 2f           mov     r30, r24
  3a:   f9 2f           mov     r31, r25
  3c:   20 83           st      Z, r18
  3e:   80 e0           ldi     r24, 0x00       ; 0
  40:   90 e0           ldi     r25, 0x00       ; 0
  42:   0f 90           pop     r0
  44:   0f 90           pop     r0
  46:   df 91           pop     r29
  48:   cf 91           pop     r28
  4a:   08 95           ret
@dylanmckay
Copy link
Member Author

It looks like this function is implemented in libgcc, which is good news for us.

I will check if we currently call this. If not, it's possible for us to emit a call to it.

@shepmaster
Copy link
Member

@shepmaster
Copy link
Member

it's possible for us to emit a call to it.

Which I'd like to be controllable, please :-)

@dylanmckay
Copy link
Member Author

dylanmckay commented Aug 24, 2017

I compiled avr-rust/blink. The interrupt table is correctly there, as well as the __ctors_end function which seems to be initialising the stack.

This __ctors_end function calls main directly after this, with no mention of __do_copy_data.

@shepmaster
Copy link
Member

avr-rust/blink

Neat, I missed that!

which seems to be initialising the stack

For reference, I have this as well. The key parts are setting the zero register and populating the stack size, then copying globals to RAM.

The interrupt table is correctly there

Which I have separately and it's all glued together with linker flags.

@dylanmckay
Copy link
Member Author

dylanmckay commented Aug 24, 2017

I've looked into this a little further and I believe the do_copy_data insertion is done based on some sort of logic implemented in the avr-gcc (not avr-ld) frontend.

I have confirmed this by taking a C program that reads a global, compiling it to an object file (which has not been linked with libgcc and so does not contain do_copy_data), and then linking it specifically with avr-ld.

This generates an executable which does not copy data to RAM, and is therefore broken.

I would like to make LLVM output code that triggers avr-gcc s do_copy_data function. I suspect we will need to place some variables into a specific section, or set a flag on the symbols in ELF.

You will still be able to avoid the insertion of the do_copy_data in the same way you can today with GCC - by using avr-ld directly, or probably also by using avr-gcc -nostartfiles.

@shepmaster
Copy link
Member

I'm mostly just mirroring what I discovered GCC to do in my research. My true hope is to have a completely non-GCC reliant solution to allow for the flexibility to have competing solutions. I'm also interested in the possibility of using lld ;-)

@dylanmckay
Copy link
Member Author

I'm definitely interested in lld as well, do you know if there are any plans to embed it into Rust and prefer it over binutils?

@dylanmckay
Copy link
Member Author

There's a small skeleton for AVR support already in lld, which is neat.

@shepmaster
Copy link
Member

linking it specifically with avr-ld.

This generates an executable which does not copy data to RAM, and is therefore broken.

That's quite a surprising result! I wonder if there's an avr-ld flag that might control the setting...

any plans to embed it into Rust

I've heard discussion about some plans here, but I'm not 100% sure what the main motivation for the broader Rust community is. I think lld might be faster than ld or gold? Maybe it's just to avoid having to install a C compiler / linker to run Hello World?

That being said, we already have to specify the specific linker now, so presumably specifying lld shouldn't be too hard...

@dylanmckay
Copy link
Member Author

I've found this in AVR-gcc (config/avr/avr.c)

avr_need_copy_data_p = (STR_PREFIX_P (name, ".data")
                         || STR_PREFIX_P (name, ".gnu.linkonce.d"));

Note that STR_PREFIX_P is a macro that is true if the first arguments starts with the second argument.

I believe this is the logic which decides whether or not we need a copy_data section.

When I disassemble Rust blink, I note that there are dozens of symbols in the output program, but there is only one variable inside a section starting with .data

00000000 l    d  .data.rel.ro.panic_loc.2       00000000 .data.rel.ro.panic_loc.2

I'm not sure why this function isn't triggering the do_copy_data, maybe it's related to the panic not actually being hit, I'm not sure.

When I modify the blink example to load from a constant static FOO, I get this

00000000 l     O .rodata._ZN5blink3FOO17h3a721bc44740f6b9E      00000004 _ZN5blink3FOO17h3a721bc44740f6b9E

So the constant is getting placed into .rodata, which also wouldn't trigger the do_copy_data.

When I change the constant static to a mutable static, the symbols do make it into the .data section.

00000000 l     O .data._ZN5blink3FOO17h3a721bc44740f6b9E        00000004 _ZN5blink3FOO17h3a721bc44740f6b9E
00000000 l    d  .data._ZN5blink3FOO17h3a721bc44740f6b9E        00000000 .data._ZN5blink3FOO17h3a721bc44740f6b9E

And now for the moment of truth - does this generate do_copy_data.

No, it does not.

@dylanmckay
Copy link
Member Author

Interestingly, it looks like avr-gcc also checks for .rodata in the same function. There is something else going on.

@dylanmckay
Copy link
Member Author

I will message the avr-gcc mailing list

@dylanmckay
Copy link
Member Author

I've looked into this more and I think I'm getting mixed up.

The C compiler of AVR-GCC is declaring do_copy_data, which leads to us calling into it via the CRT.

The only line required to trigger copying is .global __do_copy_data at some point in the assembly file. If the symbol is declared, it will be used, otherwise no data will be copied.

@dylanmckay
Copy link
Member Author

Fixed in r312905, cherry-picked in 6092c82d02b2498b413cbdee5b8617186b70425d.

@shepmaster
Copy link
Member

The only line required to trigger copying is .global __do_copy_data at some point in the assembly file. If the symbol is declared, it will be used, otherwise no data will be copied.

Can you give me the TL;DR of how to opt-in to my own non-GCC-provided code?

@dylanmckay
Copy link
Member Author

Alright, so __do_copy_data is defined in the CRT libraries.

It appears that if you declare the symbol, the CRT library will call it before it calls main. If you do not declare it, it isn't called and the function doesn't make it into the final object file.

Thus, if you don't want to use it, then don't link the CRT. I believe this works well because you're trying to avoid libc/crt code, and so there's no issue there.

In the case where no CRT is linked, it just means there is one extra symbol in the executable, but I imagine that it will get trimmed out as there are no references to it.

For the other readers, linking with avr-gcc will always link the CRT and avr-libc. This is because using a C compiler to link will result in a C-centric executable. Because of this, using avr-gcc to link with avr-llvm now will always result in data being copied to RAM and BSS being cleared, which is good.

Now, if you link with avr-ld, you do not automatically include the CRT or avr-libc. This means that doing this will not result in do_copy_data being called. In this case, you will need to roll that functionality yourself.

@shepmaster
Copy link
Member

Thanks!

linking with avr-gcc will always link the CRT and avr-libc

I've been cheating and actually using avr-gcc, so I guess I'll have to figure out how to switch... 🤞

@dylanmckay
Copy link
Member Author

dylanmckay commented Sep 12, 2017

I have a feeling that -nostartfiles will probably do it, never actually used the flag though.

@shepmaster
Copy link
Member

-nostartfiles will probably do it

That wound be convenient as I'm already using that :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants