dlopen 的 _init 與 _fini
閱讀 LinuxJournal 的文章 [
Dynamic Class Loading for C++ on Linux],我注意到其中的描述:
According to the dlopen man page, if your library exports a function called _init, this function will be executed when the library is opened. This may seem to be the ideal place to register our maker, but currently the mechanism is broken on Linux systems. The problem is a conflict with a standard linker object file, crt.o, which exports a function called _init.
man page 也提到:
The linker recognizes special symbols _init and _fini. If a dynamic library exports a routine named _init, then that code is executed after the loading, before dlopen() returns. If the dynamic library exports a routine named _fini, then that routine is called just before the library is unloaded. In case you need to avoid linking against the system startup files, this can be done by giving gcc the "-nostartfiles" parameter on the command line.
Using these routines, or the gcc -nostartfiles or -nostdlib options, is not recommended. Their use may result in undesired behavior, since the constructor/destructor routines will not be executed (unless special measures are taken).
Instead, libraries should export routines using the __attribute__((constructor)) and __attribute__((destructor)) function attributes. See the gcc info pages for information on these. Constructor routines are executed before dlopen() returns, and destructor routines are executed before dlclose() returns.
對 security 而言,這方面有頗多可著墨處,可參考 [
GNU And Its Role In Exploitation by Phactorial],回到正題,LinuxJournal 上那篇文章參考了 Jim Beveridge 提出的 "self-registering objects" 技巧,不過如果能善用 _init 與 _fini 的特性,其實可以更簡潔。
_init 與 _fini 的建議替代方案:首先是要載入的 shared object,程式碼如下:
#include <stdio.h>
#include <stdlib.h>
void __attribute__((constructor)) Double_init()
{
printf("_init invoked!\n");
}
void __attribute__((destructor)) Double_fini()
{
printf("_fini invoked!\n");
}
int Double(int arg)
{
return (arg + arg);
}
再來是主體程式:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main(int argc, char **argv)
{
void *handle;
int (*func)(int);
const char *error;
handle = dlopen ("./shared.so", RTLD_NOW);
if (! handle) {
fputs(dlerror(), stderr); exit(1);
}
func = dlsym(handle, "Double");
if ((error = dlerror()) != NULL) {
fputs(error, stderr); exit(1);
}
printf ("invoking Double(2) => %d\n", (*func)(2));
dlclose(handle);
}
這過程中,記憶體的布局變化如下:

此外,另一篇文章 [
Cross platform dynamic library initialization in C++] 也很值得參考,不過 macro 與 variable 的命名怎麼有點像 MFC style 呢?最終的 deployment,最好也能符合 LSB 規範 (Debian package 也有類似的要求),可參考 [
Developing LSB-certified applications] 一文。
由 jserv 發表於 March 5, 2006 08:05 PM