Linking two different ABIs together

Orlando Arias orlandoarias@gmail.com
Sun Aug 1 23:44:40 GMT 2021


Greetings,

On 8/1/21 6:32 PM, Alan Lehotsky wrote:
> I’m looking at an architecture where there can be multiple CPUs (with different ISA’s) running in the same address space cooperatively.
> 
> How hard would it be to modify the linker to handle this.  Assuming that each  individual source file is compiled with a gcc
> targeted to one specific ISA and then they are linked together,  
> 
> The idea would be to supply glue code when calling from one ISA to the other that takes care of the handoff of control and manages how any arguments are passed.
> 
> I’m thinking it would be pretty hard to get the binutils function pointers to deal with this - so many of the functions  would need to check which ISA they were actually trying to deal with.
> 
> I think it makes more sense to link separate DLLs and only have to insert the glue when calling to a DLL with a dissimilar ISA.  At least there, I’d have a much more restricted set of functions to have to alter and there’s already a notion of requiring shim code to effect the transfer of control.
> 
> Has anyone done something similar to this?
> 

Not quite the same to what you are doing, but the Armv8-M profile with
the security extension (TrustZone-M) works in a similar way. The way I
handle this is by having two separate `projects', one of them implements
all the functionality I need from the secure world, the other one
implements the functionality of the non-secure world. I then build the
secure world project into its own object file, exporting a file which
contains the `symbols' and addresses for the functions callable from the
non-secure world. On the non-secure world, when compiling, I use this
file so that the compiler generates a series of trampolines [or shims]
which bridge over to the secure world code. Arm requires a very specific
set of instructions to be executed before we enter secure world code in
TrustZone-M. I convert the object files into Intel Hex files then merge
them together. I could also use a linker script to do the same without
having to do any weird manipulation, all I have to make sure is that the
linker script places code in their proper place:

#define secure_function __attribute__((section(".trusted_code")))

secure_function void my_entry_function(void) {
	/* some code */
	callee_function();
	/* some code */
}

secure_function void callee_function(void) [
	/* more code */
}

Then the linker script needs to declare what to do with the
.trusted_code section

.trusted_code : {
	KEEP(*(.trusted_code))
	KEEP(*(.trusted_code.*))
} > SECURE_MEMORY

Ensuring that SECURE_MEMORY is a memory region that will be flagged as
secure only in the SAU. This can be done through a combination of the
linker script and C code.

/* ld script */
.trusted_code : {
	PROVIDE(__secure_memory_start = .);
	KEEP(*(.trusted_code))
	KEEP(*(.trusted_code.*))	
} > SECURE_MEMORY
__secure_memory_end = ORIGIN(SECURE_MEMORY) + LENGTH(SECURE_MEMORY);

/* C code */
extern unsigned int __secure_memory_start;
extern unsigned int __secure_memory_end;


Now, this works because we are in the same ISA, thus the object files
that get generated by gas are flagged the same way. The bfd linker can
go on to open them just fine and process them into an executable. It
also helps that gcc generates the shims automatically for me and places
them in the proper section.

Now, if I were to go into mixing different ISA [as in Arm + RISC-V or
something like that], the linker would complain about the format the
object files have not being the same. We can, however, work around this
problem using an intermediate step with objcopy. As an example, without
loss of generality, say we want to embed RISC-V machine code in an Arm
binary, with the Arm core bootstrapping the RISC-V core. First, we
convert the RISC-V linked binary into a flat binary file (we need to
ensure that the binary has been linked into the proper memory range
which it should occupy, not doing so requires the bootstrapping code to
fix any addresses before jumping into it):

${riscv-tools}-objcopy -Obinary risc_v_mc.elf risc_v_mc.bin

You may also want to have some sort of file which defines the addresses
of the symbols in the RISC-V binary so that you can reference them from
the Arm code later. Then, we convert the binary file into an object file
which the Arm toolchain can understand:

${arm-tools}-objcopy -Ibinary -Oelf32-littlearm \
	--rename-section \
	.data=.risc_v_image,contents,alloc,load,readonly,data \
	risc_v_mc.bin risc_v_mc.arm.o

Then, on the linker script for the Arm binary:

.risc_v_image : {
	. = ALIGN(4);
	PROVIDE(__riscv_image_start = .);
	KEEP(*(.risc_v_image))
	PROVIDE(__riscv_image_end = .);
} > RISC_V_IMAGE_DATA

and on the linker command, also pass the risc_v_mc.arm.o object file. It
will be added to the declared memory area. The bootstrapping Arm core
then could use symbol information that you extracted before to set the
entry point program counter of the RISC-V core (through some
hardware-defined interface), then release the reset of the RISC-V core
allowing it to operate. You could make things simple if you ensure that
the entry point to the RISC-V code is at __riscv_image_start from the
example above. If you have multiple entry points, you can use the symbol
data [which you can get with nm and a bit of text processing] to
automatically generate a series of shims/trampolines using a script.

Cheers,
Orlando.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_signature
Type: application/pgp-signature
Size: 195 bytes
Desc: OpenPGP digital signature
URL: <https://sourceware.org/pipermail/binutils/attachments/20210801/24d3704f/attachment-0001.sig>


More information about the Binutils mailing list