Skip to content
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

Bugfix - eliminates potential shift of data by 4 bytes. #313

Merged
merged 4 commits into from
May 31, 2022

Conversation

GideonZ
Copy link
Collaborator

@GideonZ GideonZ commented May 8, 2022

Hi!

It took me about a day to find, but I ran into the issue that the way the linker script provided the symbols for the start/end of the data segment may cause corruption. I found that depending on the size of the text segment, the variable __crt0_copy_data_src_begin was off by 4 bytes. (4 bytes less than __crt0_copy_data_dst_begin in my case where the load address and run address were the same). In my case, this caused malloc to hang. The suggested change does not show this behavior.

Tested by inserting a variable number of 'nop' instructions to modify the size of the text segment.
Also, a small addition to crt0.s; this copy does not need to take place when src and dst are the same.

Thanks for the NeoRV32!!

Best regards,
Gideon

@stnolting
Copy link
Owner

Thank you very much for the fix! I will have a closer look at your modifications.

The support for dynamic memory allocation is still in an early stage. Right now, the maximum heap size is defined in a static way (in the linker script), which does not reflect the actual application's requirements.

Furthermore, this default heap size unnecessarily increases the size of the .bss segment - even if no dynamic memory is used at all.

Do you have any idea how to make this more "portable" and "user friendly"?

@stnolting stnolting self-assigned this May 10, 2022
@stnolting stnolting added the SW software-related label May 10, 2022
@stnolting stnolting self-requested a review May 10, 2022 15:00
@stnolting stnolting added the bug Something isn't working label May 10, 2022
@stnolting
Copy link
Owner

stnolting commented May 10, 2022

I have tested your modifications with some simple newlib/malloc setup. I cannot see any differences in the symbol tables and dynamic memory allocation seems to run without problems even when using the original crt0 and linker script.

The modification you made in crt0 seems to fix a rare corner case, where there is no initialization data. This is indeed a bug that should be fixed! 👍

The modifications in the linker script do not seems to alter any functionality, but using ADDR and LOADADDR seems to be a much more "cleaner" way than adding some variables. 👍

I saw that you are using a rather old version of the processor and it's framework (v1.6.7.8). This was the very first version that supported newlib's dynamic memory allocation. Maybe the problems you were encountering were fixed in a later version. Could you update your processor version to the latest version and/or could you provide some minimal example that fails memory allocation?

@GideonZ
Copy link
Collaborator Author

GideonZ commented May 10, 2022 via email

@stnolting
Copy link
Owner

stnolting commented May 10, 2022

Thank you so much for considering my pull request. I will certainly bump my version of NeoRV32 to see if it makes any difference. (And let's hope that it didn't get any bigger in terms of LUTs.. ;)

The opposite should be the case! There were a lot of logic optimizations since v1.6.7.8 (especially in the CPU core) - plus some performance optimizations. And most important there were some (severe) bug fixes... See CHANGELOG.md.

Btw, it would be really great if you could rebase your branch as well! ;)

The modification in crt0 that I proposed is not really that much of a corner case. Any application where the data segment is loaded directly [...]

Good point! I was talking about the default setup using the default toolchain (with the default linker script). It is also possible to use different, non-NEORV32-specific start-up files and linker scripts. This has been tested using a Segger Studio based setup and also using Zephyr OS.

The failing memory allocation was a result of a shift in the data segment address (off by 4).

However, I still cannot identify the cause of this offset (yet)... Anyway, your modifications of the linker script seem just fine!

PS.. your crt0.s jumps directly to main, and skips crt0_begin / premain. This causes the static constructors of C++ objects not to be executed. ;-)

I am no expert when it comes to low-level boot mechanisms and all the C runtime related. I had to learn all the basics and I am quite happy the core is able to boot. 😉 And I am absolutely no expert when it comes to C++ and all it's constructors and stuff like that. There was just a comment on our gitter channel today targeting that issue.

It would be really great to support C++ applications - at least a bare minimum level, but as I said, I am not really familiar with that.

@jpf91
Copy link
Contributor

jpf91 commented May 19, 2022

I am no expert when it comes to low-level boot mechanisms and all the C runtime related. I had to learn all the basics and I am quite happy the core is able to boot. wink And I am absolutely no expert when it comes to C++ and all it's constructors and stuff like that. There was just a comment on our gitter channel today targeting that issue.

I'm no expert either, but is it usual for RISC-V targets to replace the C libraries _start function? neorv32's crt0.S does not seem to have much overlap with the newlib version: https://github.com/bminor/newlib/blob/master/libgloss/riscv/crt0.S

It seems to me that neorv's _start should just be called something else and should then call _start instead of main. (Not sure what crt0_begin is though, grep doesn't find anything in the riscv toolchain sources or the neorv sources).

@stnolting
Copy link
Owner

stnolting commented May 19, 2022

I'm no expert either, but is it usual for RISC-V targets to replace the C libraries _start function?

I am not sure, but I think this is highly platform-dependent. In terms of the NEORV32, the start-up code does some than "just" setting up stack, bss and data segments: besides doing a "software reset" of all IO devices, we also configure certain CSRs to setup a very simple "early trap handler".

The newlib crt0 you have linked says

Entry point for RISC-V user programs

Maybe this is intended just for user-mode programs (where we cannot access all those CSRs)? I don't know.

It seems to me that neorv's _start should just be called something else and should then call _start instead of main.

So you mean calling newlib's crt0 from NEORV32's crt0? This might work, but I am not sure right now if the makefiles actually include those start-up files. I think we are using a "free standing" setup here.

However, adding calls __libc_init_array and __libc_fini_array should be no problem, but I am not sure if the default linker script actually handles these functions.

Not sure what crt0_begin is though, grep doesn't find anything in the riscv toolchain sources or the neorv sources.

Where did you find crt0_begin ?

@jpf91
Copy link
Contributor

jpf91 commented May 19, 2022

So you mean calling newlib's crt0 from NEORV32's crt0? This might work, but I am not sure right now if the makefiles actually include those start-up files. I think we are using a "free standing" setup here.

I see, although I'm not really sure what they mean to say with "user mode". newlib is for embedded / standalone targets only, as far as I know? OTOH this code is part of libgloss, so I really don't know...

However, adding calls __libc_init_array and __libc_fini_array should be no problem, but I am not sure if the default linker script actually handles these functions.

Maybe that's a better solution. These functions are in the libc, so they should be available. (I'm not sure if atexit is available in the embedded toolchain. In C++, people often use the GCC flag -fno-use-cxa-atexit which seems somehow related.)

If we want to register __libc_fini_array using atexit like the newlib crt0 does, we probably also have to call exit after main for this to have any effect.

__libc_init_array and __libc_fini_array are defined in newlib/newlib/libc/misc/init.c and fini.c. It needs the following symbols defined: __preinit_array_start, __preinit_array_end, __init_array_start, __init_array_end, __fini_array_start, __fini_array_end

Here's an example linker script from stackoverflow for STM32:

.preinit_array     :
{
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH

I guess it should be similar for our usecase. The main aspects are that the linker scripts provides symbols bracketing the sections so the startup code can iterate through the constructors. Furthermore the symbols need to be SORTed (as it's possible to assign priorities to constructors / destructors) and they need to be KEEP (they may appear unreferenced to the linker, but should not be removed).

Where did you find crt0_begin ?

It was mentioned by @GideonZ in a comment. Maybe it's used in some other toolchains?

@GideonZ
Copy link
Collaborator Author

GideonZ commented May 19, 2022 via email

sw/common/neorv32.ld Outdated Show resolved Hide resolved
@stnolting
Copy link
Owner

stnolting commented May 19, 2022

@GideonZ

I ran into issues with sbrk and other low level stuff.

This should be fixed since version 1.6.7.8 I think. There is a newlib example program sw/example/demo_newlib/main.c that relies on sbrk and all it's friends. These are implement in sw/lib/source/syscalls.c.


Thanks for the input @GideonZ and @jpf91!

I think it should not be too difficult to add the init and fini arrays to the linker script and to call the according functions from crt0. However, I think this would add additional code (from the C runtime) even if a plain C program does not use any constructor or destructor stuff.

Btw, this is a plain C example that defines functions to be called from __libc_init_array and __libc_fini_array:

void __attribute__((constructor)) test_constructor(void) {
  neorv32_uart1_setup(19200, PARITY_NONE, FLOW_CONTROL_NONE);
  neorv32_uart1_printf("\nHello CONstructor!\n\n");
}

void __attribute__((destructor)) test_destructor(void) {
  neorv32_uart1_setup(19200, PARITY_NONE, FLOW_CONTROL_NONE);
  neorv32_uart1_printf("\nHello DEstructor!\n\n");
}

Anyway, we should open another issue for adding/discussing support of constructors and destructors.

@stnolting stnolting self-requested a review May 28, 2022 16:52
@stnolting
Copy link
Owner

I have fixed the typo and updated this branch from main. I am ok with merging this. What do you think @GideonZ?

@stnolting stnolting marked this pull request as ready for review May 28, 2022 17:04
@stnolting stnolting merged commit e79c663 into stnolting:main May 31, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working SW software-related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants