4 ms·
It didn't have to be done this way. In case somebody to fix this, here is an alternate implementation: There is a separate library, or a part of libgcc, that c
by burfog 7y ago
It didn't have to be done this way. In case somebody to fix this, here is an alternate implementation:
There is a separate library, or a part of libgcc, that can be mapped in multiple times. This allows trampolines without any limit other than memory. Alternately, just decide how many you want in advance, and place them into the executable.
Trampolines are conceptually in an array, to be allocated one by one, but there are actually two arrays because we'd want to separate the executable part from the non-executable part. On a 64-bit architecture with 4096-byte pages we might have a read-write page with 256 pairs of pointers, an execute-only page with 256 trampoline functions (which use those pairs of pointers), and possibly another execute-only page with helper code. The trampoline functions can use the return address on the stack to determine location, though x86_64 also offers RIP-relative addressing that could be used.
These arrays could be done as thread-private data or done with locking. Pick your poison. The allocation changes accordingly, along with the space requirements and cache-line bouncing and all. Be sure to update setjmp and longjmp as required.
There is also a much simpler answer to the usual security problems. If the stack will become executable and the option hasn't been given to explicitly allow this, linking should always fail at every step. Propagating the lack of a no-exec flag should be impossible without confirmation of intent.
- Gibbon1 7y agoI use them and I don't care about the trampolines because I don't have protected memory. Having used them, they are really useful. Leading me to think that the biggest thing missing from C is closures. Fix that and C becomes a lot better language.
- psyclobe 7y agoThat and destructors, maybe some constexpr evaluated stuff like C++ is doing now, and then really C would become way funner to code in.
- quotemstr 7y agoSo just write in C++ and don't use the parts you don't like. I've never understood the almost-reflexive aversion to C++ you see in some low-level circles. If you don't want huge Java-style class hierarchies, don't make them. If you don't want templates to bloat your code, use templates sparingly and take advantage of type erasure. You can make a C++ program look like anything you want.
- convolvatron 7y agoyes you can. but if you walk into a C++ shop, every developer has thrown in a few of their favorite tricks. everyone says they are using a 'sane subset', but it always seems to turn out to be 'most of it'.
- quotemstr 7y agoThe people who use the phrase "sane subset" are those who in my experience distort C++ beyond recognition by banning particular features of the language wholesale rather than shaping the programs that they write to fit the environment. You should be using the whole C++ language. There's no such thing as a "sane subset", because all of the language has "sane" uses. What you want is to create good programs.
- saagarjha 7y agoIt’s a GNU extension, and not quite the same as a destructor, but __attribute__((cleanup)) might be useful for your use case.
- saagarjha 7y agoIf you don’t know about it already, Apple has a C extension for closures that they call blocks: https://en.wikipedia.org/wiki/Blocks_(C_language_extension) https://en.wikipedia.org/wiki/Blocks_(C_language_extension). They’re incompatible with function pointers, though.
- convolvatron 7y agotwo approaches i use - each closure is a function which takes a number of saved arguments and a number of new arguments. construct a set of macros to define a closure structure. this structure contains a function pointer to the closure, plus all the saved arguments. another macro defines function to take the new arguments and construct a complete call to the handler function with the saved arguments. this solution carries the type information for all the arguments and flags at compile time attempts to call the closure with non-unifying new arguments. this general approach is from Sergei T and is used quite heavily here: https://github.com/nanovms/nanos https://github.com/nanovms/nanos dynamically synthesize a function to shift the new arguments to the right in the call, and fill in the saved arguments from immediates. this is faster and application can use normal function calls, but its not type safe. working on a little c->c front end that maintains type safety these are both really implementations of partial application rather than general closures. they are both heap based which allows them to be used for general callbacks without worrying about lifetime, but they become a big memory management problem. with the exception of the memory issues - i think c+closures is a great environment for systems programming.
- quotemstr 7y ago> It didn't have to be done this way. That ought to be a tagline at the bottom of every man page in section 2 and 3. It really disappoints me that making fundamental improvements to the GNU/ELF/etc.-family programming model is so difficult. I've already commented about the benefits of a two-level namespace. The article we're discussing is about a bad default in linker interfaces. There are tons of other systemic problems that nobody is interested in solving because the default response to every suggestion is "no". Consider, for example, symbol interposition: why should programs go through the GOT to access their own symbols? Yes, yes, in theory, you can use LD_PRELOAD to interpose symbols, except you actually can't, because Clang aggressively inlines [1], which means that whether your interposed function is actually called in a given context is a crapshoot. We're paying for intra-library symbol interposition even though it's become unreliable. Yes, you can set symbol visibility to hidden by default or use a linker script to trim your export list, but most people don't know to do that. Why is the default so bad? Why do we have to live with bad defaults for decades? [1] https://lists.llvm.org/pipermail/llvm-dev/2016-November/107625.html https://lists.llvm.org/pipermail/llvm-dev/2016-November/1076...
- codetrotter 7y ago> It really disappoints me that making fundamental improvements to the GNU/ELF/etc.-family programming model is so difficult. [...] There are tons of other systemic problems that nobody is interested in solving because the default response to every suggestion is "no". The expertise of yourself and others on this subject might be welcome in upcoming Unix-like OSes that are being written like: - https://www.redox-os.org/ https://www.redox-os.org/ - https://github.com/SerenityOS/serenity https://github.com/SerenityOS/serenity I am involved in neither of those but I follow them both with interest.
- quotemstr 7y agoIt's an attractive idea. Another project I've been considering is implementing a unified buffer cache in OpenBSD. It's just really hard to find motivation to do stuff like that when user counts are so low and you still risk running into "culture of no"-related roadblocks.
- 7y ago
- deleted 7y ago[deleted]
- saagarjha 7y agoThis sounds a lot like how imp_implementationWithBlock from the Objective-C runtime works, because it needs to run on iOS where executable stack is a big no-no: http://landonf.bikemonkey.org/code/objc/imp_implementationWithBlock.20110413.html http://landonf.bikemonkey.org/code/objc/imp_implementationWi...
- lultimouomo 7y agoI don't understand why trampolines are needed at all, I would think you could pass the stack reference (or the captured variables references) as extra argument to the nested function, and pass them around together with it's address as a struct. Isn't this what lambdas do in C++?
- saagarjha 7y agoBecause your callee is stupid, and it doesn't know about your extra argument. The pointer you give it needs to look like a normal function pointer in every way: it can't take different parameters, it can't return something else, and it can't move around. Almost all languages with first-class closures do it the way you mention, but the limitation is that you can't use them with code that's not expecting it.
- lultimouomo 7y agoThat makes sense, a C++ templated callee can know that it's calling what is essentially a member function, a C non-templated function can't.