-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Zig-based alternative to linker scripts #3206
Comments
I like where this is going.
It's always possible that this feature would ultimately generate a linker script (without necessarily exposing it to the programmer) for use with LLD. |
I thought I'd play around with this idea. Is there a way to write a file at compile time? I couldn't find anything on that, and just putting standard file IO code in comptime doesn't seem to work. Another question is if there's a good way of extracting symbols and their values (for constants) in the build system before linking? |
It's not possible by design to write a file in comptime code. I don't think that's needed anyway for this feature. The feature might need access to metadata about symbols in object files, assembled assembly files, and compiled C files. |
Excellent. I looked a bit at doing build scripts, but it seemed the documentation there was lacking so it was a bit difficult. This looks very much like what I was thinking. What I would like is for the information used to generate the linker script be available both at runtime and in the build script. But I suppose that's just a matter of having it in a shared zig file which is imported in both build.zig and the program itself? I also looked at the source code of LLD to see if Zig could somehow direct linking without a linker script at all. It looks like it should be pretty simple, it's just a matter of setting up "OutputSections": The tricky part is that some of the sections attributes are defined as dynamic expressions, e.g: As far as I understand is that this is because an output section could be located relative to the address and size of sections that precede it, and their size depends on the number and size of the input sections that contain them. The address is evaluated after all the sections has been set up. So the problem of defining a format for a Zig linker script boils down to how you decide to generate those expressions.
Where ExprValue is just a struct that evaluates to an address, possibly relative to a section and possibly with alignment. Could a function in build.zig (or imported in build.zig) somehow be passed along to LLD and be assigned as an |
I think the cleanest way to do this would be to use |
Whilst pondering your comments, I added the generated files to the repo for your perusal. https://github.com/markfirmware/zig-vector-table/tree/master/generated/generated_linker_files |
Regarding build_options, that might be a nice place to put things but they need to be primitive types - see #3127. Regarding working more directly with lld, that will take more effort than I have available. It does beg the question though of how much of the linker machinery needs to exposed to the bare metal programmer? They need to know flash vs ram and that code, read-only data and initial mutable data go in flash, bss needs to get set to 0 in ram and there can be data in ram with undefined initial values. Then there is the root, the vector table, in flash that drags in everything else. These are abstractions that must be understood and it might be possible to understood them without referring to a linker. I dont think the .ARM.exidx section is needed - I need to study that further. |
Just a piece of data: custom linker scripts can be very useful for e.g. JITs on AMD64. For instance, if I'm emulating a 32-bit system, and I can ensure that no host data (the application itself) resides at an address used by the system I'm emulating, I can map physical memory to the emulated device 1:1 and never have to translate addresses (which can significantly improve translation times). |
There's a lot more you can do with linker scripts, even:
|
Ok, so for some more complex use cases, linker scripts will still be valuable. |
I'll share what we use linker scripts for where I work. We design microcontrollers, and these are the features we use in our linker scripts:
There's a big use-case for both us and our customers for customising the linker script when it comes to laying out static content, heap and stack in memory. The RAM is divided into blocks. Each block can be shut off to conserve power, so you may want to avoid blocks if you're not using all of the RAM. Or you can locate temporary data in blocks that will be shut off in sleep mode. There's also a cross-bar between each peripheral with DMA and each RAM block, so if you make sure DMA buffers is located in a specific block not otherwise used by the CPU, you can guarantee that the peripheral does not compete with the CPU for access to RAM. |
Thank you @skyfex. I think the simple case at https://github.com/markfirmware/zig-vector-table/blob/master/generated/generated_linker_files/generated_linker_script.ld is consistent with a subset of your use case with the exception that there are no alignment directives. I need to check to see if the relevant input sections are already marked with alignment requirements that are then respected by the linker. The articulation of memory into blocks for security, power and mutation is something I will study next. The most complex memory structure in a zig linker script that I've seen is https://github.com/vegecode/BurnedHead/blob/master/firmware/linker.ld @vegecode |
Don't forget case where a data structure must be placed at a specific location. I generally have done this by placing the structure in a custom section and then using the linker script to place that section at the desired location. |
for the record: if we want to get rid of linker scripts, we have to be able to model this in order to supported embedded systems:
This means: Collect all symbols from the section So consider this code:
so if we now link this with the script above considering little endian, we get the following result: 0x1000: 0x0A 0x00 0x00 0x00 # this is i
0x1004: 0x00 0x02 0x00 0x00 # this is p
So the linked address is different from the stored address, which is required for embedded systems to initialize the RAM at boot time. Zig code also needs to be able to get query section boundaries, builtin functions would be useful here: const sectionInfo = @section(".data");
@TypeOf(sectionInfo) == struct {
name: []const u8
virtual_memory: []u8,
stored_memory: []u8,
}; |
I made a concept art for https://gist.github.com/MasterQ32/65e7a158a600a94dad22ab5a15be080f#file-linkerscript-zig The core idea here is that you can have a imperative way of controlling the linking. The script here displays the power to modify section data in the linking step and computing stuff like checksums and such, also a fine grained control over load vs. virt ual addresses We could implement linker scripts on top of that or just a possibility to use zig to declare the info for the linker. My implementation idea would be to have a |
What is zld? Is there a future for generated .ld files? I have updated my approach to explicate input sections https://github.com/markfirmware/zig-vector-table/blob/a2c56a793792f6e078813107387fab389121e415/system_model.zig#L1-L7 I am still using simple declarative structures (the next step in complexity might be to use a builder pattern.) The only artificial non-zig steps that the embedded programmer must employ are to understand these compiler section names and add them to system_model.zig by trial and error. I am guessing that they are determined by llvm at this point. |
Related to this issue, check out the readme of mold, specifically the part where it talks about linker scripts. |
Thanks. I note from the linker scripts section:
This is mainly what my .ld generation does. |
I have collected the for Kernels most notable linker scripts from FreeRTOS, Zephyr, Linux and a multi-target one at ikskuh/link.zig-concept-art#1 (comment) For reference:
@markfirmware I do see that you do not generate the linker script anymore in commit markfirmware/zig-vector-table@797e17d. What technical reasons did you have for not generating the linker script anymore? |
In embedded, we deal with microcontrollers (MCU) which embed microprocessors (MCPU). So, when we say ARM Cortex-M4 MCU we actually mean an MCU which embeds one variant of ARM Cortex-M4 MCPU. It could embed any other ARM MCPU or RISC-V MCPU... Everything starts when you power on the device! MCPU (ARM Cortex-M4 for example) turns on and spends some meaningles cycles to prepare for interpretation of ARM instructions and then it starts interpretting them... When it starts interpretting, we first feed it our custom ARM Cortex-M4 "startup code" (written using "unified ARM assembly" and "ARM instruction set" supported by MCPU). But this "startup code" is like a punch card! If holes (individual ARM instructions from the supported ARM instruction set) are at the wrong positions (memmory locations) it will crash! This is what linker script has to solve. It enables programmers to use labels inside the "startup code" and linker script enables us to decide at which memmory location to position a single or multiple ARM instructions. If this is not possible in Zig, then it can not be used on embedded without OS (baremetal). It could still be used on embeedded with an OS (which has its own "startup code" that it feeds to the MCPU at startup). |
Sorry for the cheesy title, feel free to change it
With the current state and plans of Zig, it's looking like the only case where you need a language other than Zig for writing firmware/software, is if you need a custom linker script. What's worse, the documentation for GCC/LLD linker scripts are not all that great, and they can be hard to understand. The scripting language is also not really powerful enough on its own to do more complex things, so I've seen a case where the C preprocessor has been used to generate linker scripts.
If linker scripts could be replaced with Zig code, Zig would end up being an extremely elegant solution for embedded firmware in particular.
As far as I see it, there's two parts to solving this:
Linking can not be handled by compile time evaluation, but compile time constant values/structures should be available to the linker scripts. Some values, such as fixed memory regions and addresses, could be calculated at compile time. These values are generally useful for both the Zig runtime code itself, and to the linker script. There may be some work to define compile time functions, and pre-defined variable names or struct types that the linker can use.
Define a way for Zig code to run at link time, and define functions and data
structures that gives the Zig linker code information about the sizes and flags of sections , and instruct the linker how code and data should be laid out in memory.
This feature may be dependent on the Zig self-hosting linker #1535, but it would be nice if a proof-of-concept could be done with LLD. Does LLD have an API that can be used or does it only work with linker scripts?
The text was updated successfully, but these errors were encountered: