Island/Linux fails to pass structures properly in libraries

This is a repost of the issue of libraries throwing a segfault regarding the GC when attempting to pass structures to functions under Linux. This specifically occurs when accessing an array in the structure, allocated either statically or via a pointer. The console project loads the library dynamically, which is in the binary folder.

Source of test library:
Library2.zip (4.3 MB)

Dynamically load library in Console app:
ConsoleApplication3_dynamic.zip (6.2 MB)

Can we hold thisnuntal we have the issue wit the array sizes sorted out? im expecting this might be related, ie if the arrays are allocated on the heap when thye should not be, passing this between dll and exe, which both have their own heap management, might cause trouble and be “as expected”.

I assume when you pass a proven-flat structure (ie one where sizeof matches what you expect), no error happens?

1 Like

Well the sizes do match as 80 in both the library and calling code (either the Mercury console app, or a C++ app compiled in GCC). However, assuming that you mean a structure without any arrays (static or dynamic), the code still segfaults without array references. Flattening the structure to just include 3 doubles and a byte still gives the same error.

Flat struct in library:

Module Test

Structure test
	 Dim x,y,z As Double
End Structure

Structure test2
	' Dim v As test
     Dim x, y, z As Double
	 Dim t As Byte
End Structure

    <DLLExport("testFunc")>
    '<SymbolName("test")>
    Public Sub testFunc(t As test2)
		writeLn("Struct values: ")
        writeLn($"{t.t}")
        writeLn($"{t.x} {t.y} {t.z}")
		/*writeLn($"{t.v(0).x} {t.v(0).y} {t.v(0).z}")
    	writeLn($"{t.v(1).x} {t.v(1).y} {t.v(1).z}")
        writeLn($"{t.v(2).x} {t.v(2).y} {t.v(2).z}")*/
        
    End Sub
   
End Module

C++ console test (same result in Mercury console test):


#include <iostream>
#include <cstdint>
#include <dlfcn.h>

struct test{
	double x,y,z;
};


struct test2{
	double x,y,z;
	uint8_t b;
};

using std::cout;

int main()
{
	test2 t;
	t.b = 216;
	t.x = 241.15125;
	t.y = 3.1459;
	t.z = 2.71;
	void (*testFunc)(test2);

	auto l = dlopen("./Library2.so", RTLD_LAZY);
	if(l){
		testFunc = (void(*)(test2))dlsym(l, "testFunc");
		cout << "Testing" << "\n" << sizeof(t) << "\n";
		
		testFunc(t);
		dlclose(l);
	}

	return 0;	
}

Console output (error is from library code):

Testing
32
Struct values:
0
Segmentation fault

GDB Output:

Reading symbols from test...
(No debugging symbols found in test)
(gdb) run
Starting program: /home/sollapse/test
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Testing
32

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff78a20d2 in GC_find_limit_with_bound () from ./Library2.so

Okay. if I can get one completed test case that has all the pieces in one place and does not use arrays, that would be appreciated and I’ll log an issue to have the team have a look.

Both projects are included with changes and are referenced from the Console solution.

Library2 dynamic_noarrays.zip (2.2 MB)

Were the included files sufficient to test?

Didn’t get around to it this morning yet :wink:

FTR on Mac your project works, the output is

32
Struct values: 
219
0.108378266973482 0.411983670335927 0.533651378242468
A: 7767163 B: 1460584
Add: 32

i’ll assume that as as expected.

Trying to test on Linux, now.

1 Like

hi, tested on ubuntu right now:
imagen

1 Like

That’s odd. I suspect this may have something to do with libc within WSL2. I’ll try from a Linux VM instead.

1 Like

On Ubuntu 23.04 running in a VM, I’ve updated the code to include the original arrays which function properly. Although the same crash is occurring with the inline assembly functions. This works normally on Windows, and I’ve updated the code to highlight the different results received using either InlineAsm or InternalCalls.VoidAsm. I’ve used a fixed value to show the error more clearly and avoid any overflows from the RandomInt function.

ConsoleApplication3(inlineasm).zip (2.2 MB)

Assembly error (External is InlineAsm, Inline is InternalCalls.VoidAsm):

GDB Error:

This segfault is occurring in the main console binary with the added inline assembly code. I mentioned this in a previous post questioning how to properly use the clobbers/constraints in the assembly commands.

Sorry, but InlineAsm() is definitely an “you’re on your own, kid. you always were” kind of feature…

I completely understand that, however are your constraints functioning in regards to the library functions. I never got a response in regards to that question?

Edit: All errors are fixed now after changing the order of the expected registers for SystemV’s convention.

I don’t understand what that means…

cool.

It would be in regards to the Island assembly functions as listed in the documentation:

VoidAsm

Inline assembly; this call is replaced by the AT&T syntax asm passed to this method.

  • [Oxygene]
  • [C#]
  • [Swift]
  • [Java]
  • [Mercury]
class method VoidAsm(aAsm: String; aConstraints: String; aSideEffects: Boolean; aAlign: Boolean; params aArgs: array of Object)

Parameters:

  • aAsm: The assembly to use; Note that $ has to be escaped with $ (so $$1 for decimal 1 in AT&T); $0 refers to the first parameter passed to aArgs, etc.
  • aConstraints: A clang compatible constraint string
  • aSideEffects: If true this asm block has side effects not visible in the constraint list
  • aAlign: If true, the stack has to be aligned before entering this blcok
  • aArgs: Argument array passed to asm.

For instance, the aConstraints parameter should be a string formatted in a LLVM IR compatible manner (ex. “+r” means a general register should be used as an input and output to a supplied variable). The variables to be used by assembly should be passed in aArgs. So if I wanted to return an integer defined as Dim a As Integer from assembly I would expect to do something like the following:

Dim a As Integer = 0

InternalCalls.VoidAsm( " mov $0, %ebx
                         add $$1, %ebx
                         mov %ebx, $0", "+r", false, false, a)

I’ve attempted to test this, yet it seems that the variables are not being read (both on Windows and Linux). However, I noticed this has been used in your Island RTL in a couple of places: (https://github.com/remobjects/IslandRTL/blob/92deb3d6ae9123129957e308377040080fa064a5/Source/WindowsHelpers.pas#L1449).

On second read, and review of the RTL code, is it possible that only the function parameters are able to be passed instead of local variables?

I really don’t know, sorry. As I said, InternalCalls.VoidAsm is for internal use, really, and not meant for public consumption.

Understood. Although it’s great to have as an option if there’s any necessity for additional low-level code, especially regarding mobile platforms. I was able to get access to static defined variables by calling the symbol directly which serves the same purpose. It’s more tedious, but works.

1 Like