pinty icon indicating copy to clipboard operation
pinty copied to clipboard

Closure serialization does not work when sending the bytearray over to another process

Open q10 opened this issue 10 years ago • 3 comments

I tried the closure serialization library you provided at github.com/darabos/pinty/blob/master/pinty.h, and while it appears to work locally (serialize() followed by load()), as soon as I send the bytearray over the wire or to another process, the second process has trouble de-serializing and executing the closure. This happens even if the two processes are on the same machine and compiled by the same compiler. Is this expected, and can this be fixed (or is this a limitation of C++)?

q10 avatar Nov 26 '14 04:11 q10

Hi, thanks for checking out "Pinty"!

There are two more limitation you might not have accounted for. You can send the serialized closure to another process, but both processes have to run the same binary and you need to disable ASLR.

The serialized closure is made up of two parts: the function address and the data captured by the closure. I think the function address is transferable to another process if it's the same binary and ASLR is off. The data captured by the closure is transferable if it does not contain pointers. (I guess that's limitation number 3 then.)

I've tried to improve the demo a bit in https://github.com/darabos/pinty/commit/92f325de79fe1c6571715af4801d6c2504b63bb1. Can you reproduce the example from README.md, or does that also fail for you? What operating system do you use?

darabos avatar Nov 26 '14 13:11 darabos

Fortunately, I am able to run the pinty example without problems.

I am running on a Mac machine with clang 3.5. What is the command/compile flag I need to disable ASLR?

I see what was wrong with my setup, then. I was building a client and server, and I had compiled the client and the server separately, so while the tcp hex dump was exactly the same, there was a segfault when casting it back from byte-array to closure.

In the future, I can compile that serializable closure under one common library once, and then import/link against it when compiling the server and client. Should that work? Or must they be both the same binary, not just the same *.so file? Fortunately, I have made it so all my closures capture variables by value, so there shouldn't be a problem with pointers

I would think that since a closure is a data structure like any other class/object or POD, there shouldn't be a problem with transporting entire functions in serialized form to different processes. Is it because C++ lacks an ABI or is it that C++ is a compiled language that I am encountering this problem?

Thanks for getting back to me so quick!

q10 avatar Nov 26 '14 19:11 q10

I am running on a Mac machine with clang 3.5. What is the command/compile flag I need to disable ASLR?

As I understand it, on the Mac ASLR is disabled by a binary flag (an attribute baked into the binary). You can set it through a linker flag: -Wl,-no_pie (See http://stackoverflow.com/questions/23897963/documented-way-to-disable-aslr-on-os-x for discussion.) I've just tried changing run.mac.sh to use clang++ instead of g++ and it seems to work just the same.

In the future, I can compile that serializable closure under one common library once, and then import/link against it when compiling the server and client. Should that work? Or must they be both the same binary, not just the same *.so file?

Good question. I don't know. It all depends on where the function will get placed in the process's address space. Since *.so files are compiled with -fPIC they are position independent and can be placed anywhere. I think it's up to the dynamic linker to place them in the address space, and I don't know how it picks the location. (I'm not an expert at this, I just wrote this code as a learning experience.)

I would think that since a closure is a data structure like any other class/object or POD, there shouldn't be a problem with transporting entire functions in serialized form to different processes. Is it because C++ lacks an ABI or is it that C++ is a compiled language that I am encountering this problem?

You mean, to transport the machine instructions of the function? I suppose it's a theoretical possibility, but would be really hard to do. Even if the function has no memory references (to other functions or data), we only know where it starts, not where it ends. (Perhaps there is a way to figure that out, if you know the compiler well.)

To me the limitation to run the same binary everywhere does not seem so bad. I'd just give my binary a --master/--slave flag and have them behave completely differently.

darabos avatar Nov 26 '14 22:11 darabos