 | Level: Introductory Nam Keung (namkeung@us.ibm.com), IBM Senior Technical Consultant , IBM Gary Hook (ghook@us.ibm.com), Senior Technical Consultant, IBM
15 Mar 2004 Do you need to resolve the segmentation fault while using the atexit handler function on AIX® 5.2? This article explores techniques that can be used to resolve program crashes resulting from missing atexit handlers.
Overview
What is an atexit function? It specifies a function to be called when a program terminates. The atexit subroutine registers a function to be called for the cleanup process at normal termination. The atexit() subroutine adds the func to a list of functions without parameters at normal termination of the program. Normal termination occurs because of a call to the exit subroutine, or a return statement in the main function. The exit subroutine terminates the calling process after invoking the standard I/O library _cleanup function to flush any buffered output. It also calls any previous functions registered by the atexit subroutine. The register "routine" is invoked during the loading or unloading of a module.
The problem
What happens if an application combines the use of atexit() function with dlopen()? That application program receives a segmentation error during the exit of the program. If a dlopen-ed shared module registers an atexit function and is then dlclose-d, the process will crash when exiting because the function is no longer available when the exit() code processes the at-exit functions. The segmentation error is not the normal, expected behavior of using an atexit handler, but is a potential side effect when using dynamic loading via dlopen.
The following steps outline a classic example of the problem when using the atexit handler:
- An application builds a shared library (shared.cpp) with a function that registers the atexit function.
- The application main program (main) calls
dlopen to load the shared library.
- The main program uses
dlsym to locate a function, which is already registered by the atexit handler.
- A call to
dlclose is made to unload the shared library, and finally the program exits.
- During the program exit, the function registered by atexit handler from the shared library is unloaded, which causes a core dump (illegal instruction).
- shared.cpp
-
#include <stdio.h>
#include <stdlib.h>
#include <sys/limits.h>
void myexit(void)
{
printf("\nInside my exit\n");
}
int myfunction1()
{
printf("Inside function 1\n");
// Register atexit function
atexit(myexit);
printf("Registered an exit function\n");
return 0;
}
|
- main.cpp
-
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char *mylib;
mylib = (char *)dlopen("./libshared1.so", RTLD_NOW);
if(mylib == 0)
{
char *myptr;
myptr = dlerror();
printf("%s - NULL pointer", myptr);
exit(-1);
}
else
{
printf("Loaded library\n");
}
int (*myfunc)();
myfunc = (int (*)())dlsym((void *)mylib, "myfunction1__Fv");
// Call myfunction1() from the loaded library
(*myfunc)();
dlclose((void *)mylib);
return 0;
}
|
- makefile
-
EXE_FLAGS= -g
SHLIB_FLAGS= -G -qmkshrobj
CC=xlC_r
## DO not change below
INCLUDEPATH = -I.
DEFS = $(STDDEFS) -DDEBUG
LDFLAGS = -g
CPPFLAGS = -g
LIBS = -L/lib -ldl
### Can change below this line, do not add/delete any targets
OUTPUTDIR = ./
TARGET = $(OUTPUTDIR)/myexit
OBJSMAIN = \
main.o
SHARED1OBJ = \
shared.o
all:: shared1 $(TARGET)
shared1: $(SHARED1OBJ)
$(CC) $(SHLIB_FLAGS) $(LDFLAGS) $(SHARED1OBJ)
-o./libshared1.so -L/lib -ldl
ar rv libshared1.a libshared1.so
clean:
rm -rf *.o *.a *.so myexit
$(TARGET): $(OBJSMAIN)
$(CC) $(EXE_FLAGS) $(OBJSMAIN) -o $(TARGET) $(LIBS)
shared.o: ./shared.cpp
$(CC) -c $(CPPFLAGS) $(INCLUDEPATH) -o $@ ./shared.cpp
main.o: ./main.cpp
$(CC) -c $(CPPFLAGS) $(INCLUDEPATH) -o $@ ./main.cpp
|
Output from AIX 5.2:
> myexit
Loaded library
Inside function 1
Registered an exit function
Segmentation fault(coredump)
|
If we step into the debugger:
> dbx myexit
Type 'help' for help.
[using memory image in core]
reading symbolic information ...
Segmentation fault in ptrgl.$PTRGL [/usr/lib/libc.a] at 0xd01d61c8 ($t1)
0xd01d61c8 ($PTRGL) 800b0000 lwz r0,0x0(r11)
(dbx) print 0xd01d61c8
-803380792
(dbx) print ptrgl.$PTRGL
1. ptrgl.$PTRGL [myexit]
2. ptrgl.$PTRGL [/usr/lib/libpthreads.a]
3. ptrgl.$PTRGL [/usr/lib/libC.a]
4. ptrgl.$PTRGL [/usr/lib/libC.a]
5. ptrgl.$PTRGL [/usr/lib/libC.a]
6. ptrgl.$PTRGL [/usr/lib/libc.a]
Select one of [1 - 6]: |
When the atexit registered function (myexit) is called during the program exit(), the myexit() is already unloaded. Therefore, its address is invalid and causes a segmentation error to occur.
Solving the problem
So how do we fix the myexit function registered by the atexit handler? There are several ways to resolve this issue:
- AIX provides the option
-binitfini:: as a termination entry point to be called. Use –binitfini::xxxx to resolve the atexit register function. (xxxx is the myexit__Fv function for the above sample code.) The –binitfini::myexit__Fv binder option is added to the ld statement that specifies the termination function for a module, where ::Termination is a termination routine. The myexit__Fv will be invoked when the module is unloaded. As long as you know the registered function, you can use the ld command with the initfini option.
- Using
unatexit() to unregister the function
We can have an init()/destroy() function paired as a dlopen or dlclose function. Since the standard libc.a library provides the atexit/unatexit functions, an application can use the unatexit function call to invoke the registered function and unload the module within the application program.
Ideally, the module would have been built using the linker's -binitfini: option. A "fini" function would be added to the module that is automatically invoked at module unload or program exit time. The function called by atexit() would need to communicate with the fini function, so that if the program is exiting, the atexit function does the work, and the fini function becomes a no-op. Or, if the module is being unloaded, the fini function can call unatexit() and also invoke the function if desired. The choice is the programmer's.
However, without knowing which function was registered by the atexit function, both option 1 and 2 will not resolve the core dump caused by atexit. In this case, you need to look at option 3, commenting out dlcose, to bypass this problem.
In the sample program, do you need to call unatexit to unregister (myexit) before calling the dlclose routine or use the -binitfini option? This is up to the application implementation. Sometimes it is incorrect to use unatexit for shared libraries because unatexit may not be present at the exit time if the application has been dlclose'd. Shared libraries should always use -binitfini:: termination functions. It is insufficient to say you will not dlclose the object since you cannot dictate that to all users' libraries.
In AIX 5.2, unatexit is not declared in the stdlib.h. You need to install APAR IY51367. Otherwise, a declaration error will occur for unatexit.
- Comment out the dlclose function
If there is no way of knowing or controlling the registered function by atexit, then a user application must resort to option 3. You can avoid the segmentation error by not calling the dlclose routine, which is used to remove the access to a module loaded with the dlopen call. Access to the dependent modules of the unloaded module is removed as well.
Are there any side effects of not calling dlcose? Yes: no "unload" operation will occur. When the process is terminating, the module will be processed as required (destructors, termination functions) along with all other exit functionality. The key difference here is whether this particular module is intended to be repeatedly added to and removed from the address space, and if it contains data objects that should be re-instantiated each time. By not unloading the module its data will never get "refreshed". If this is a concern, one of the solutions above must be explored.
 |
Summary
The first and second solutions involve the register functions to be invoked during the loading and unloading of a module. For option one, using -binitfini::xxxx at link time will resolve the problem. The article described how you can register the routine to be called when the module is unloaded. In option two, you can also set up the previously registered functions by calling unatexit to resolve the segmentation error. Both solutions only help if you know which function was registered by the atexit function. The third solution does not require any unloading operations. If the functions registered by atexit are unknown, then the unloading processes are bypassed by not calling the dlclose routine.
About the authors  | |  | Nam Keung is a senior programmer who has worked in the area of AIX communication development, AIX multimedia, SOM/DSOM development and Java performance. His current assignment involves helping ISVs in application design, deploying applications, performance tuning, and education for the pSeries platform. You can contact Nam at namkeung@us.ibm.com. |
 | |  | Gary R. Hook is a senior technical consultant at IBM, providing application development, porting, and technical assistance to independent software vendors. Mr. Hook's professional experience focuses on Unix-based application development. Upon joining IBM in 1990, he worked with the AIX Technical Support center in Southlake, Texas, providing consulting and technical support services to customers, with an emphasis upon AIX application architecture. Now residing in Austin, Mr. Hook was a member of the AIX Kernel Development team from 1995 through 2000, specializing in the AIX linker, loader, and general application development tools. You can contact him at ghook@us.ibm.com. |
Rate this page
|  |