diff options
| author | Adrian Kummerlaender | 2016-02-17 15:02:48 +0100 | 
|---|---|---|
| committer | Adrian Kummerlaender | 2016-02-17 15:02:48 +0100 | 
| commit | af756d78ac042a2eed2417c5250d4b675d43bf93 (patch) | |
| tree | aedc585d49a5d7eb5549e81c78db43f08dedc9a3 /src/init | |
| parent | 98f68dd9bb0318acfaaf7f2e7ad571a19729a8bb (diff) | |
| download | change-af756d78ac042a2eed2417c5250d4b675d43bf93.tar change-af756d78ac042a2eed2417c5250d4b675d43bf93.tar.gz change-af756d78ac042a2eed2417c5250d4b675d43bf93.tar.bz2 change-af756d78ac042a2eed2417c5250d4b675d43bf93.tar.lz change-af756d78ac042a2eed2417c5250d4b675d43bf93.tar.xz change-af756d78ac042a2eed2417c5250d4b675d43bf93.tar.zst change-af756d78ac042a2eed2417c5250d4b675d43bf93.zip | |
Implement static allocator for initialization
The previous interposition logic based on plain usage of `dlsym` analogously to various online examples led to a deadlock during _neovim_ startup. This deadlock was caused by _neovim_'s custom memory allocation library _jemalloc_ because it calls `mmap` during its initialization phase. The problem with calling `mmap` during initialization is that this already leads to executing `libChangeLog`'s `mmap` version whoes static `actual_mmap` function pointer is not initialized at this point in time. This is detected and leads to a call to `dlsym` to remedy this situation. Sadly `dlsym` in turn requires memory allocation using `calloc` which leads us back to initializing _jemalloc_ and as such to a deadlock.
I first saw this as a bug in _jemalloc_ which seemed to be confirmed by a short search in my search engine of choice. This prompted me to create an appropriate [bug report](https://github.com/jemalloc/jemalloc/issues/329) which was dismissed as a problem in the way `mmap` was interposed and not as a bug in the library. Thus it seems to be accepted practice that it is not the responsibility of a custom memory allocator to cater to the initialization needs of other libraries relying on function interposition. This is of course a valid position as the whole issue is a kind of _chicken and egg_ problem where both sides can be argued.
To cut to the chase I was left with the only option of working around this deadlock by adapting `libChangeLog` to call `dlsym` without relying on the wrapped application's memory allocator of choice. The most straight forward way to do this is to provide another custom memory allocator alongside the _payload_ function interpositions of `mmap` and friends.
`init/alloc.cc` implements such a selectively transparent memory allocator that offers a small static buffer for usage in the context of executing `dlsym`.The choice between forwarding memory allocation requests to the wrapped application's allocator and using the static buffer is governed by `init::dlsymContext`. This tiny helper class maintains an `dlsym_level` counter by posing as a scope guard.
The end result of this extension to `libChangeLog` is that it now also works with applications using _jemalloc_ such as _neovim_ and should overall be much more robust during its initialization phase.
Diffstat (limited to 'src/init')
| -rw-r--r-- | src/init/alloc.cc | 50 | ||||
| -rw-r--r-- | src/init/alloc.h | 22 | 
2 files changed, 72 insertions, 0 deletions
| diff --git a/src/init/alloc.cc b/src/init/alloc.cc new file mode 100644 index 0000000..d18917b --- /dev/null +++ b/src/init/alloc.cc @@ -0,0 +1,50 @@ +#include "alloc.h" + +#include <cstdio> +#include <cstdint> + +namespace init { + +std::uint8_t dlsym_level   = 0; +size_t       buffer_offset = 0; +char         buffer[4096]; + +void* static_malloc(size_t size) { +	if ( buffer_offset + size >= sizeof(buffer) ) { +		std::fprintf(stderr, "static allocator out of memory: %lu\n", buffer_offset); +		exit(1); +	} + +	buffer_offset += size; + +	return buffer + buffer_offset; +} + +void* static_calloc(size_t block, size_t size) { +	void* const mem = static_malloc(block * size); + +	for ( size_t i = 0; i < (block * size); ++i ) { +		*(static_cast<char* const>(mem) + i) = '\0'; +	} + +	return mem; +} + +bool from_static_buffer(void* ptr) { +	return static_cast<char*>(ptr) >= buffer +	    && static_cast<char*>(ptr) <= buffer + sizeof(buffer); +} + +dlsymContext::dlsymContext() { +	++dlsym_level; +} + +dlsymContext::~dlsymContext() { +	--dlsym_level; +} + +bool dlsymContext::is_active() { +	return dlsym_level > 0; +} + +} diff --git a/src/init/alloc.h b/src/init/alloc.h new file mode 100644 index 0000000..cd1228d --- /dev/null +++ b/src/init/alloc.h @@ -0,0 +1,22 @@ +#ifndef CHANGE_SRC_INIT_ALLOC_H_ +#define CHANGE_SRC_INIT_ALLOC_H_ + +#include <cstdlib> + +namespace init { + +void* static_malloc(size_t size); +void* static_calloc(size_t block, size_t size); + +bool from_static_buffer(void* ptr); + +struct dlsymContext { +	dlsymContext(); +	~dlsymContext(); + +	static bool is_active(); +}; + +} + +#endif  // CHANGE_SRC_INIT_ALLOC_H_ | 
