LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Unexpected stack offsets with CLFN

Solved!
Go to solution

LV 2022 x64

 

I'm needing to call a function that has a struct of 2x U32s as members and is passed by-value and not as a pointer. However if I try sending 2x U32s LabVIEW seems to be doing some sort of packing accounting which it shouldn't be doing for separate arguments, and the Network member of the struct vanishes and other arguments are offset oddly. I can get the call to work correctly if instead of sending a cluster of 2 U32s, I send the 2 U32s packed into a single U64. I purport that there's a bug in how the CLFN is managing the data sizes, NI told me I was using it incorrectly even though I sent them a method that does work despite not being the expected method and their response doesn't work correctly.

 

I'm hoping the hive mind may be able to confirm a bug or tell me what I'm misunderstanding about the first method I tried using for the call.

 

The struct definition is:

typedef struct {
	uint32_t NetworkMask;
	uint32_t Network;
} AddressTranslation_t;

 

And the function declaration is:

LV_EXPORT 
extern uint32_t TestCall(
	uint32_t TimeoutMs,
	const struct sockaddr* BindAddress,
	const AddressTranslation_t RequestTranslation,
	Config_t** Configurations,
	uint32_t* ConfigurationLength,
	char** ErrorDescription
);

With the definition:

{
    try {
        sockaddr_in* sock = (sockaddr_in*)BindAddress;
        *ConfigurationLength = sock->sin_addr.S_un.S_addr;
        CreateErrorDesc("Greetings from DLL", ErrorDescription);
    }
    catch (std::exception e) {
        CreateErrorDesc(e.what(), ErrorDescription);
        return false;
    }

    return (RequestTranslation.Network >> 1) + (RequestTranslation.NetworkMask);
}

The function basically uses the pointers that are passed in to verify some of the other values make it through as expected and creates a return value that ensures the struct members make it intact.

 

Here is how I thought it would work but doesn't:

IlluminatedG_0-1677863071995.png

IlluminatedG_2-1677863182747.png

 

Here is how I'm able to get it to work:

IlluminatedG_1-1677863124177.png

IlluminatedG_3-1677863232286.png

 

And here is what NI sent me back:

IlluminatedG_4-1677863512900.png

IlluminatedG_5-1677863554822.png

 

The entire thing is attached. I'll post a screenshot of the call stack once my visual studio subscription is straightened out if that'd be helpful to show what LabVIEW is doing wrong about the first method and hopefully the function prototype of the third method alone is enough to recognize why that won't work, which is apparently what the shared library import wizard generates that I wasn't able to run on the full library and hadn't bothered trying on this smaller reproduction.

~ The wizard formerly known as DerrickB ~
Gradatim Ferociter
0 Kudos
Message 1 of 11
(1,721 Views)

You are likely using 64-bit LabVIEW (and DLL) and there a parameter is of course always a 64-bit entity, so your 2 * 32-bit elements need to be packed in a 64-bit integer. 

Rolf Kalbermatter
My Blog
0 Kudos
Message 2 of 11
(1,702 Views)

Really? LabVIEW is incapable of putting U32s on the stack? Or any other size? How is it supposed to call a function with a byte or I16 or U32 as a function argument?

~ The wizard formerly known as DerrickB ~
Gradatim Ferociter
0 Kudos
Message 3 of 11
(1,681 Views)

I'll also point out that the timeout first argument is a U32 and doesn't cause oddities so I'm going to have to call bull on that statement 🙂

~ The wizard formerly known as DerrickB ~
Gradatim Ferociter
0 Kudos
Message 4 of 11
(1,679 Views)
Solution
Accepted by topic author IlluminatedG

Read about ABI logic. The Windows  64-bit ABI has a mixed register/stack parameter passing called fastcall. First four parameters are passed in 4 64-bit registers, with 4 64-bit shadow locations on the stack for them and the rest is aligned on 64-bit boundaries on the stack. Parameters not fitting into 64-bit are passed as reference (64-bit pointer) If a 8-bit char parameter has to be passed it is put into a 64-bit register or stack location, possibly sign extended for signed integers. Your struct with 2 32-bit values fits into a single 64-bit value, is a single parameter and is therefore passed as a single 64-bit value in a register or on the stack.

 

For 32-bit LabVIEW it would be distributed over two 32-bit parameters. Passing structs by value is a very strange beast indeed and causes this kind of differences.

Rolf Kalbermatter
My Blog
Message 5 of 11
(1,669 Views)

Thanks for that topic name Rolf, that's the kind of thing I was hoping for. And thanks for the edits and more clarification.

 

After looking at the stacks again I'm pretty sure I see where my confusion came from. At first glance it looked like things weren't aligned on 8 byte boundaries:

notworkingcallstack.png

 

But now I'm thinking that first 0x1DB sequence is just stale memory left in place from a previous operation.

 

Taking a look again at the working method there's an obvious 8 byte alignment happening which I think I missed the extra "padding" on the timeout value the first time around:

stack2.png

 

~ The wizard formerly known as DerrickB ~
Gradatim Ferociter
Message 6 of 11
(1,626 Views)

Yes, the padding is not required to be initialized for prototyped parameters. The assumption is that since there is a prototype, both the caller and callee agree on the type and don’t care about the contents of the padding.

Also I’m not entirely sure the caller is required to write the first 4 parameters on the stack as they are passed in the registers. But I suppose it’s safer to do it anyways just to be sure.

Rolf Kalbermatter
My Blog
0 Kudos
Message 7 of 11
(1,553 Views)

Yeah that's what lead to the "bullshit" remark. They're in memory on the stack so it didn't add up with some of what was being said. The built DLL I'm debugging is using the values from the stack. (I found the memory location with the expression "&TimeoutMs" in the address field)

Looking at MSDN __fastcall uses registers from the caller but the callee can still use the stack which makes the register-sized-alignment make sense. I'm not finding where it says things default to fastcall though and I assumed stdcall... which this obvious isn't since stack items aren't reversed. And I just noticed where it says x64 ignores fastcall so I guess I'm back to not understanding the "why" but empirically see what's happening now, so *shrug*.

 

This wizard has so much more to learn 😆

~ The wizard formerly known as DerrickB ~
Gradatim Ferociter
0 Kudos
Message 8 of 11
(1,519 Views)

Something you read is off. Fastcall is the default for Win64, unless it is an object method which still uses thiscall I believe.

 

https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170

 

Stdcall is not a thing in Win64 anymore and is the thing that should be ignored by a compiler and at least MSVC does so by default. Same about cdecl.

 

https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170

Rolf Kalbermatter
My Blog
0 Kudos
Message 9 of 11
(1,440 Views)

@rolfk wrote:

Parameters not fitting into 64-bit are passed as reference (64-bit pointer)


So that means on 64 bit Windows a struct passed by value in the function declaration is actually passed by reference if its size exceeds 8 bytes? I wish I'd known that before..

 

0 Kudos
Message 10 of 11
(1,425 Views)