I'm writing a dynamic array for a personal project and am trying to unit test all the functions. I'm trying to write a unit test for util_dyn_array_check_index() but am having problems doing so because I made the design decision to call exit(-1) if there is an index out of bounds. I want to check in my unit test that it calls exit() when provided with an invalid index. But if I ever give it an invalid index, it just exits my testing program. Is it possible to somehow catch that a call to exit() was thrown, or redefine exit() in my testing program to prevent it from ending the tests?
From this answer I've looked into atexit(), but it looks like that doesn't stop an exit, just performs one or more user-defined functions before exiting. This doesn't work for me because I have other tests to run after this one. My last thought is I could make util_dyn_array_check_index() a macro instead of a function, and redefine exit() to be a different function in my testing program, but I'd rather not make it a macro if I can avoid it.
Here's my code:
The details of this struct don't really matter, just provided for completeness
//basically a Vec<T>
typedef struct {
//a pointer to the data stored
void * data;
//the width of the elements to be stored in bytes
size_t stride;
//the number of elements stored
size_t len;
//the number of elements able to be stored without reallocating
size_t capacity;
} util_dyn_array;
Here is the function I want to test.
//exits with -1 if index is out of bounds
inline void util_dyn_array_check_index(util_dyn_array * self, size_t index) {
if (index >= self->len) {
exit(-1);
}
return;
}
Here is a skeleton of what I would like the test to be (omitted some macro magic I'm using to make writing tests nicer, for clarity).
bool test_dyn_array_check_index() {
util_dyn_array vector = util_dyn_array_new(sizeof(int), 16);
for(int i = 0; i < 16; i++) {
util_dyn_array_push(&vector, (void*)&i);
}
for(int i = 0; i < 16; i++) {
//if nothing happens, its successful
util_dyn_array_check_index(&vector, i);
}
//somehow check that it calls exit without letting it crash my program
{
util_dyn_array_check_index(&vector, 16);
}
return true;
}
Obviously I could change my code to return a bool or write to errno, but I'd prefer it to exit as its usually an unrecoverable bug.
It is UB to define another exit() in standard C (both as a function and as a macro if the header is included). In many environments, you are likely to be able to get away with it, though.
With that said, doing such a thing makes no sense here, because we are talking about exit(). The function is not expecting to continue execution after that, so that pretty much forces you to replace it with a longjmp() or go with textual replacement to convert it to a return (assuming a void return type). In both cases, it means you need to assume the function is not leaving things in a broken state (like holding some resource). That is a lot to assume, but it may be a reasonable way out if your unit testing framework is meant to be tied to this particular project.
Instead of trying to modify the behavior of the tested function, I suggest you add support for running tests in their own process to your test framework. There are many advantages apart from being able to test things like these. For instance, you get the ability of running tests in parallel for free and isolates many side-effects between them.
Another solution: use an #ifdef macro to call a different function in your debug build.
#ifdef DEBUG
#define exit_badindex(...) my_debug_function(__VA_ARGS__)
#else
#define exit_badindex(...) exit(__VA_ARGS__)
#ifndef NDEBUG
#warning NDEBUG undefined in non-DEBUG build: my_debug_function will not be called
#endif
#endif
Run the code in a fork and check the exit value of the fork.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/wait.h>
void util_dyn_array_check_index() {
exit(-1);
}
int main(void) {
pid_t pid = fork();
assert(pid >= 0);
if( pid == 0 ) {
util_dyn_array_check_index();
exit(0);
}
else {
int child_status;
wait(&child_status);
printf("util_dyn_array_check_index exited with %d\n", WEXITSTATUS(child_status));
}
}
Note that the exit status will be 255 because despite accepting an integer POSIX exit statuses are unsigned. Consider using exit(1) instead or define an ARGUMENT_ERROR_EXIT_STATUS macro.
Note that I used an assert to check if my fork failed. This might be a better way to implement your checks.
inline void util_dyn_array_check_index(util_dyn_array * self, size_t index) {
assert(index < self->len);
}
This will provide more information on error, and it can be switched off in production for performance.
Assertion failed: (index < self->len), function util_dyn_array_check_index, file test.c, line 9.
assert calls abort. In your tests you'd close stderr to avoid the assert message from cluttering up the output, and check that WTERMSIG(status) == SIGABRT.
void util_dyn_array_check_index() {
assert(43 < 42);
}
int main(void) {
pid_t pid = fork();
assert(pid >= 0);
if( pid == 0 ) {
// Suppress the assert output
fclose(stderr);
util_dyn_array_check_index();
exit(0);
}
else {
int status;
wait(&status);
if( WTERMSIG(status) == SIGABRT ) {
puts("Pass");
}
else {
puts("Fail");
}
}
}
If it's for unit testing, you don't need to physically catch the call to exit().
Before the solution, I would like to suggest redesigning your library. You have a few alternatives:
Having util_dyn_array include a callback that is called if out-of-bound mode is encountered, and default it to exit(1) (not exit(-1), that doesn't work well when the program is called from a shell).
Having a global out-of-bound handler (which again defaults to exit(1)), and allow the program to change the handler at runtime by calling something like set_oob_handler(new_handler).
Doing integration tests instead of unit tests. As suggested by multiple people here, if the library can exit or crash, this goes to the realm of integration (with the calling process/OS).
My solution:
main.c:
#include <stdio.h>
void func(void);
int main(int argc, char **argv)
{
printf("starting\n");
func();
printf("ending\n");
}
something.c:
void my_exit(int status)
{
printf("my_exit(%d)\n", status);
#ifdef UNIT_TEST
printf("captured exit(%d)\n", status); // you can even choose to call a global callback here, only in unit tests.
#else
exit(status);
#endif
}
void func(void) {
my_exit(1);
}
makefile:
# these targets are MUTUALLY EXCLUSIVE!!
release:
cc -g -c -fpic something.c
cc -shared -o libsomething.so something.o
cc -g -o main main.c -L. -lsomething
fortest:
cc -DUNIT_TEST=1 -g -c -fpic something.c
cc -shared -o libsomething.so something.o
cc -g -o main main.c -L. -lsomething
$ make release
cc -g -c -fpic something.c
[...]
$ LD_LIBRARY_PATH=. ./main
starting
my_exit(1)
$ make fortest
cc -DUNIT_TEST=1 -g -c -fpic something.c
[...]
$ LD_LIBRARY_PATH=. ./main
starting
my_exit(1)
captured exit(1)
ending
(note that this was tested on Linux, I don't have a Mac to test on, so minor makefile modifications might be required).
The exit function is a weak symbol, so you can create your own copy of the function to catch the case where it gets called. Additionally, you can make use of setjmp and longjmp in your test code to detect a proper call to exit:
For example:
#include "file_to_test.c"
static int expected_code; // the expected value a tested function passes to exit
static int should_exit; // 1 if exit should have been called
static int done; // set to 1 to prevent stubbing behavior and actually exit
static jmp_buf jump_env;
static int rslt;
#define test_assert(x) (rslt = rslt && (x))
// stub function
void exit(int code)
{
if (!done)
{
test_assert(should_exit==1);
test_assert(expected_code==code);
longjmp(jump_env, 1);
}
else
{
_exit(code);
}
}
bool test_dyn_array_check_index() {
int jmp_rval;
done = 0;
rslt = 1;
util_dyn_array vector = util_dyn_array_new(sizeof(int), 16);
for(int i = 0; i < 16; i++) {
util_dyn_array_push(&vector, (void*)&i);
}
for(int i = 0; i < 16; i++) {
//if nothing happens, its successful
should_exit = 0;
if (!(jmp_rval=setjmp(jump_env)))
{
util_dyn_array_check_index(&vector, i);
}
test_assert(jmp_rval==0);
}
// should call exit(-1)
{
should_exit = 1;
expected_code = 2;
if (!(jmp_rval=setjmp(jump_env)))
{
util_dyn_array_check_index(&vector, 16);
}
test_assert(jmp_rval==1);
}
done = 1
return rslt;
}
Before calling a function that could call exit, call setjmp to set a jump point. The stubbed exit function then checks whether exit should have been called and with which exit code, then calls longjmp to jump back out to the test.
If exit was called then the return value of setjmp is 1, indicating it came from a call to longjmp. If not longjmp is not called and the return value of setjmp will be 0 after the function returns.
Related
The Actual Problem
I have an executable that by default uses EGL and SDL 1.2 to handle graphics and user input respectively. Using LD_PRELOAD, I have replaced both with GLFW.
This works normally unless the user has installed the Wayland version of GLFW, which depends on EGL itself. Because all the EGL calls are either stubbed to do nothing or call GLFW equivalents, it doesn't work (ie. eglSwapBuffers calls glfwSwapBuffers which calls eglSwapBuffers and so on). I can't remove the EGL stubs because then it would call both EGL and GLFW and the main executable is closed-source so I can't modify that.
Is there any way to make LD_PRELOAD affect the main executable but not GLFW? Or any other solution to obtain the same effect?
The Simplified Problem
I made a simplified example to demonstrate the problem.
Main Executable:
#include <stdio.h>
extern void do_something();
int main() {
do_something();
fputs("testing B\n", stderr);
}
Shared Library:
#include <stdio.h>
void do_something() {
fputs("testing A\n", stderr);
}
Preloaded Library:
#include <stdio.h>
int fputs(const char *str, FILE *file) {
// Do Nothing
return 0;
}
When the preloaded library isn't used, the output is:
testing A
testing B
When it is used, the output is nothing.
I'm looking for a way to make the preloaded library only affect the main executable, that the output would be:
testing A
Thank you!
You can check if the return address is in the executable or the library, and then call either the "real" function or do your stub code, like this:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
#include <stdio.h>
#include <stdlib.h>
static struct {
ElfW(Addr) start, end;
} *segments;
static int n;
static int (*real_fputs)(const char *, FILE *);
static int callback(struct dl_phdr_info *info, size_t size, void *data) {
n = info->dlpi_phnum;
segments = malloc(n * sizeof *segments);
for(int i = 0; i < n; ++i) {
segments[i].start = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr;
segments[i].end = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr + info->dlpi_phdr[i].p_memsz;
}
return 1;
}
__attribute__((__constructor__))
static void setup(void) {
real_fputs = dlsym(RTLD_NEXT, "fputs");
dl_iterate_phdr(callback, NULL);
}
__attribute__((__destructor__))
static void teardown(void) {
free(segments);
}
__attribute__((__noinline__))
int fputs(const char *str, FILE *file) {
ElfW(Addr) addr = (ElfW(Addr))__builtin_extract_return_addr(__builtin_return_address(0));
for(int i = 0; i < n; ++i) {
if(addr >= segments[i].start && addr < segments[i].end) {
// Do Nothing
return 0;
}
}
return real_fputs(str, file);
}
This has some caveats, though. For example, if your executable calls a library function that tail-calls a function you're hooking, then this will incorrectly consider that library call an executable call. (You could mitigate this problem by adding wrappers for those library functions too, that unconditionally forward to the "real" function, and compiling the wrapper code with -fno-optimize-sibling-calls.) Also, there's no way to distinguish whether anonymous executable memory (e.g., JITted code) originally came from the executable or a library.
To test this, save my code as hook_fputs.c, your main executable as main.c, and your shared library as libfoo.c. Then run these commands:
clang -fPIC -shared hook_fputs.c -ldl -o hook_fputs.so
clang -fPIC -shared libfoo.c -o libfoo.so
clang main.c ./libfoo.so
LD_PRELOAD=./hook_fputs.so ./a.out
Implement the interposing library separately for the two cases.
Create a wrapper script or program that uses ldd to find out the exact EGL library version and their paths the target binary is dynamically linked against; then, using ldd on the the GLFW library, to find out whether it is linked against EGL or not. Finally, have it execute the target binary with the path to the appropriate interposing library in LD_PRELOAD environment variable.
I want to specify during run-time to ignore a function call for a function (which is of course defined) inside my executable. Please suggest some methodology for doing the same in C language on Linux.
Probably the best you can do is something like this:
// Filename mycode.c
int main()
{
// ...
#ifndef SOME_MACRO
someFUnction();
#endif
//...
}
int someFUnction()
{
// does something
}
To exclude the function call in main, you need to compile with
gcc -DSOME_MACRO mycode.c
If you will compile simply as
gcc mycode.c
then the function call will be enabled.
You cannot ignore function calls at runtime, you either call the function or you don't.
But let's assume for the sake of this answer that there exists a condition under which the function gets called and at least another condition under which the function is not called.
You can tell the program these conditions in several ways, for example per command-line, change of environment/file, and probably a long list more. For simplicity let's use the command-line and give the conditions in form of a argument to the executable. Additionally, because it is simple and short, use a signal.
File optional_functions.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
static void a(void)
{
puts("Function \"a\" called");
}
static void b(void)
{
puts("Function \"b\" called");
}
static void c(void)
{
puts("Function \"c\" called (by signal SIGINT)");
}
#include<signal.h>
#include<unistd.h>
static void signal_handler(int signal_number)
{
if (signal_number == SIGINT) {
c();
exit(EXIT_SUCCESS);
}
}
int main(int argc, char **argv)
{
void *dynlib;
void (*function_d) (void);
char *dynlib_error;
if (argc == 2) {
if (argv[1][0] == 'a') {
a();
} else if (argv[1][0] == 'b') {
b();
} else if (argv[1][0] == 'd') {
puts("External function \"d\" wanted, loading library");
dynlib = dlopen("libfunctiond.so", RTLD_LAZY);
if (dynlib == NULL) {
fprintf(stderr, "Failed loading lib: %s\n", dlerror());
exit(EXIT_FAILURE);
}
*(void **) (&function_d) = dlsym(dynlib, "d");
dynlib_error = dlerror();
if (dynlib_error != NULL) {
fprintf(stderr, "Failed calling function \"d\" fom lib: %s\n",
dynlib_error);
exit(EXIT_FAILURE);
}
(*function_d) ();
} else {
fprintf(stderr, "A function named \"%c\" does not exist, bailing out\n",
argv[1][0]);
exit(EXIT_FAILURE);
}
} else {
if (signal(SIGINT, signal_handler) == SIG_ERR) {
fprintf(stderr, "signal catching failed, bailing out\n");
exit(EXIT_FAILURE);
}
sleep(5);
puts("Signal catching timed out, assuming no function wanted in the first place.");
}
exit(EXIT_SUCCESS);
}
File functiond.h
#ifndef FUNCTIOND_H
#define FUNCTIOND_H
void d(void);
#endif
File functiond.c
#include <stdio.h>
#include "functiond.h"
void d(void)
{
puts("Function \"d\" called and says hello from the library");
}
Compile as
clang -Weverything -fPIC -c functiond.c
clang -shared -Wl,-soname,libfunctiond.so.1 -o libfunctiond.so.1.0 functiond.o
ln -sf libfunctiond.so.1.0 libfunctiond.so.1
ln -sf libfunctiond.so.1 libfunctiond.so
clang -Weverything -o optional_functions optional_functions.c example.c -ldl
Run it
$ ./optional_functions # waiting 5 seconds
Signal catching timed out, assuming no function wanted in the first place.
$ ./optional_functions # press CTRL+c in less than 5 seonds
^CFunction "c" called (by signal SIGINT)
$ ./optional_functions 1
A function named "1" does not exist, bailing out.
$ ./optional_functions a
Function "a" called
$ ./optional_functions b
Function "b" called
$ ./optional_functions d
External function "d" wanted, loading library
Failed loading lib: libfunctiond.so: cannot open shared object file: No such file or directory
That was expected. Either give dlopen() the complete path to the library or let the environment variable LD_LIBRARY_PATH do the job:
$ LD_LIBRARY_PATH=. ./optional_functions d
External function "d" wanted, loading library
Function "d" called and says hello from the library
It is not the proper way to make, install and and use dynamic libraries, of course, but again: for the sake of simplicity…
I just read about init and fini sections in ELF files and gave it a try:
#include <stdio.h>
int main(){
puts("main");
return 0;
}
void init(){
puts("init");
}
void fini(){
puts("fini");
}
If I do gcc -Wl,-init,init -Wl,-fini,fini foo.c and run the result the "init" part is not printed:
$ ./a.out
main
fini
Did the init part not run, or was it not able to print somehow?
Is there a any "official" documentation about the init/fini stuff?
man ld says:
-init=name
When creating an ELF executable or shared object, call
NAME when the executable or shared object is loaded, by
setting DT_INIT to the address of the function. By
default, the linker uses "_init" as the function to call.
Shouldn't that mean, that it would be enough to name the init function _init? (If I do gcc complains about multiple definition.)
Don't do that; let your compiler and linker fill in the sections as they see fit.
Instead, mark your functions with the appropriate function attributes, so that the compiler and linker will put them in the correct sections.
For example,
static void before_main(void) __attribute__((constructor));
static void after_main(void) __attribute__((destructor));
static void before_main(void)
{
/* This is run before main() */
}
static void after_main(void)
{
/* This is run after main() returns (or exit() is called) */
}
You can also assign a priority (say, __attribute__((constructor (300)))), an integer between 101 and 65535, inclusive, with functions having a smaller priority number run first.
Note that for illustration, I marked the functions static. That is, the functions won't be visible outside the file scope. The functions do not need to be exported symbols to be automatically called.
For testing, I suggest saving the following in a separate file, say tructor.c:
#include <unistd.h>
#include <string.h>
#include <errno.h>
static int outfd = -1;
static void wrout(const char *const string)
{
if (string && *string && outfd != -1) {
const char *p = string;
const char *const q = string + strlen(string);
while (p < q) {
ssize_t n = write(outfd, p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1 || errno != EINTR)
break;
}
}
}
void before_main(void) __attribute__((constructor (101)));
void before_main(void)
{
int saved_errno = errno;
/* This is run before main() */
outfd = dup(STDERR_FILENO);
wrout("Before main()\n");
errno = saved_errno;
}
static void after_main(void) __attribute__((destructor (65535)));
static void after_main(void)
{
int saved_errno = errno;
/* This is run after main() returns (or exit() is called) */
wrout("After main()\n");
errno = saved_errno;
}
so you can compile and link it as part of any program or library. To compile it as a shared library, use e.g.
gcc -Wall -Wextra -fPIC -shared tructor.c -Wl,-soname,libtructor.so -o libtructor.so
and you can interpose it into any dynamically linked command or binary using
LD_PRELOAD=./libtructor.so some-command-or-binary
The functions keep errno unchanged, although it should not matter in practice, and use the low-level write() syscall to output the messages to standard error. The initial standard error is duplicated to a new descriptor, because in many instances, the standard error itself gets closed before the last global destructor -- our destructor here -- gets run.
(Some paranoid binaries, typically security sensitive ones, close all descriptors they don't know about, so you might not see the After main() message in all cases.)
It is not a bug in ld but in the glibc startup code for the main executable. For shared objects the function set by the -init option is called.
This is the commit to ld adding the options -init and -fini.
The _init function of the program isn't called from file glibc-2.21/elf/dl-init.c:58 by the DT_INIT entry by the dynamic linker, but called from __libc_csu_init in file glibc-2.21/csu/elf-init.c:83 by the main executable.
That is, the function pointer in DT_INIT of the program is ignored by the startup.
If you compile with -static, fini isn't called, too.
DT_INIT and DT_FINI should definitely not be used, because they are old-style, see line 255.
The following works:
#include <stdio.h>
static void preinit(int argc, char **argv, char **envp) {
puts(__FUNCTION__);
}
static void init(int argc, char **argv, char **envp) {
puts(__FUNCTION__);
}
static void fini(void) {
puts(__FUNCTION__);
}
__attribute__((section(".preinit_array"), used)) static typeof(preinit) *preinit_p = preinit;
__attribute__((section(".init_array"), used)) static typeof(init) *init_p = init;
__attribute__((section(".fini_array"), used)) static typeof(fini) *fini_p = fini;
int main(void) {
puts(__FUNCTION__);
return 0;
}
$ gcc -Wall a.c
$ ./a.out
preinit
init
main
fini
$
Language: C
Operating System: Red Hat EL
Starting with a "for instance":
Assume I have two libraries: libJUMP.so and libSIT.so.
JUMP contains the function jump() and similarly SIT contains the function sit()
I have an application that I want to provide to different people; they can either get the jump() feature, the sit() feature, or both. However, I would like to NOT use #ifdef if at all possible.
Header for libJUMP.so:
#ifndef JUMP_H_
#define JUMP_H_
#define JUMP_ENABLED
void jump();
#endif /* JUMP_H_ */
Header for libSIT.so:
#ifndef SIT_H_
#define SIT_H_
#define SIT_ENABLED
void sit();
#endif /* SIT_H_ */
I have an application:
#include "jump.h"
#include "sit.h"
int main()
{
// #ifdef JUMP_ENABLED
jump();
// #endif /* JUMP_ENABLED */
// #ifdef SIT_ENABLED
sit();
// #endif /* SIT_ENABLED */
}
So:
Is there a way to do this without using #ifdef? Is there a better way at all?
I have heard we could do this by compiling with both SO libraries, and if one is missing when I run the application on the target system, it could just exclude the feature automatically (using some combination of dlopen() and dlsym()?) Any easy examples, if this is indeed correct? An example with my code from above, if possible :D?
If this is a stupid question, or just not possible, please feel free to tell me so. If there is a similar question that this would be considered a duplicate of, let me know and I will delete this post.
Consider these three files. First, jump.c:
#include <stdio.h>
int jump(const double height)
{
fflush(stdout);
fprintf(stderr, "Jumping %.3g meters.\n", height);
fflush(stderr);
return 0;
}
Second, sit.c:
#include <stdio.h>
int sit(void)
{
fflush(stdout);
fprintf(stderr, "Sitting down.\n");
fflush(stderr);
return 0;
}
Third, example.c to use one or both of the above, depending on whether they (as libjump.so or libsit.so, respectively) exist in the current working directory:
#include <stdio.h>
#include <dlfcn.h>
static const char *jump_lib_path = "./libjump.so";
static int (*jump)(const double) = NULL;
static const char *sit_lib_path = "./libsit.so";
static int (*sit)(void) = NULL;
static void load_dynamic_libraries(void)
{
void *handle;
handle = dlopen(jump_lib_path, RTLD_NOW | RTLD_LOCAL);
if (handle) {
jump = dlsym(handle, "jump");
/* If no jump symbol, we don't need the library at all. */
if (!jump)
dlclose(handle);
}
handle = dlopen(sit_lib_path, RTLD_NOW | RTLD_LOCAL);
if (handle) {
sit = dlsym(handle, "sit");
/* If no sit symbol, the library is useless. */
if (!sit)
dlclose(handle);
}
}
int main(void)
{
int retval;
load_dynamic_libraries();
if (jump) {
printf("Calling 'jump(2.0)':\n");
retval = jump(2.0);
printf("Returned %d.\n\n", retval);
} else
printf("'jump()' is not available.\n\n");
if (sit) {
printf("Calling 'sit()':\n");
retval = sit();
printf("Returned %d.\n\n", retval);
} else
printf("'sit()' is not available.\n\n");
return 0;
}
Let's first compile and run the example program:
gcc -Wall -O2 example.c -ldl -o example
./example
The program outputs that neither jump() or sit() are available. Let's compile jump.c into a dynamic library, libjump.so, and then run the example again:
gcc -Wall -O2 -fPIC -shared jump.c -Wl,-soname,libjump.so -o libjump.so
./example
Now, the jump() function works. Let's compile sit.c, too, and run the example a final time:
gcc -Wall -O2 -fPIC -shared jump.c -Wl,-soname,libsit.so -o libsit.so
./example
Here, both functions get called, and everything just works.
In example.c, jump and sit are function pointers. We initialize them to NULL, so that we can use if (jump) to check if jump points to a valid function.
The load_dynamic_libraries() function uses dlopen() and dlsym() to obtain the function pointers. Note that if the dynamic library is opened successfully, and the necessary symbol is found, we do not dlclose() it because we want to keep the dynamic library in memory. (We only dlclose() it if it looks like it is not the kind of library we want.)
If you want to avoid the if (jump) and if (sit) clauses, you can use stubs like
int unsupported_jump(const double height)
{
return ENOTSUP;
}
int unsupported_sit(void)
{
return ENOTSUP;
}
and at the end of load_dynamic_libraries(), divert the functions to the stubs instead of NULL pointers, i.e.
if (!jump)
jump = unsupported_jump;
if (!sit)
sit = unsupported_sit;
Note that function-like interfaces are easiest to use, because the function pointer acts as the effective prototype. If you need objects, I recommend using getter functions. Objects do work just fine, as long as you remember that dlsym() returns a pointer to the object; using a getter function, that is explicit in the getter function pointer type.
Plug-in interfaces commonly have a single function (say, int properties(struct plugin *const props, const int version)), which is used to populate a structure of function and object pointers. The application supplies the version of the structure it uses, and the plug-in function returns either success or failure, depending on whether it can populate the structure to accommodate that version.
As plug-ins are typically stored in a single directory (/usr/lib/yourapp/plugins/ is very common), you can trivially load all plugins by using opendir() and readdir() to scan the file names in the plug-in directory one by one, dlopen()ing each one, obtaining the properties() function pointer, and calling it to see what kinds of services the plugin provides; typically creating an array or a linked list of the plugin structures.
All of this is very, very simple and straightforward in Linux, as you can see. If you want a specific plug-in functionality example, I recommend you pose that as a separate question, with more details on what kind of functionality the interface should expose -- the exact data structures and function prototypes do depend very much on what kind of application we have at hand.
Questions? Comments?
I have this c program:
#include <sys/types.h>
#include "ourhdr.h"
int glob = 6; /* external variable in initialized data */
char buf[] = "a write to stdout\n";
int
main(void)
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
err_sys("write error");
printf("before fork\n"); /* we don't flush stdout */
if ( (pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) { /* child */
glob++; /* modify variables */
var++;
} else
sleep(2); /* parent */
printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
exit(0);
}
In the header file "ourhdr.h" (located in the same folder) i defined several functions, such as err_sys().
I get this error while compiling with gcc:
In function "main":
undefined reference to "err_sys"
How can i get this working? I can post the header file here if needed. Thank you.
** EDIT: ** This is the ourhdr.h file: http://pastebin.com/fMUiG4zU
You have included the header file with the declaration of err_sys. Ensure you have also an implementation that is passed to the linker. Background:
The compiler compiles a module to an object file:
g++ -c modul1.c generates modul1.o
g++ -c modul2.c generates modul2.o
Each of the modules can reference functions that are defined in a included header file. Up to here, no actual implementation of that function is needed.
In the next step, the linker links toghether all object files (and libraries):
g++ modul1.o modul2.o generates ./a.out or similar
In the second step, an implementation for all used functions is needed. Be sure you provided it!
(P.S.: Some compilers allow compiling and linking with one command, if you have multiple modules you can perhaps add them to a single gcc-call. I'd recomment to use make though)
I think what's likely to happen is that you have a header, ourhdr.h, containing the definition of the function, but the implementation is in a different file: ourhdr.c. In that case, if you try to compile without including ourhdr.c you'll get a reference error:
$ gcc main.c
/bin/ld: /tmp/ccVzNF6w.o: in function `main':
main.c:(.text+0x38): undefined reference to `err_sys'
To fix it, you need to compile like this:
$ gcc main.c ourhdr.c
Another option is to define the body of the function in ourhdr.h:
// ourhdr.h
#include <stdio.h>
void err_sys(const char* str);
void err_sys(const char* str) {
fprintf(stderr, "%s\n", str);
}
In that case, gcc main.c should work.
For me, it is compiling and working fine if you write the function name is written correctly.