• No results found

Dynamically Loading a Shared Library

In document Learn C the Hard Way (Page 143-149)

To do this, I will create two source files: One will be used to make a libex29.so library, the other will be a program called ex29 that can load this library and run functions from it.

libex29.c

Click he re to vie w code imag e

1 #include <stdio.h>

2 #include <ctype.h>

3 #include "dbg.h"

4 5

6 int print_a_message(const char *msg) 7 {

8 printf("A STRING: %s\n", msg);

9

10 return 0;

11 } 12 13

14 int uppercase(const char *msg) 15 {

16 int i = 0;

17

18 // BUG: \0 termination problems 19 for(i = 0; msg[i] != '\0'; i++) { 20 printf("%c", toupper(msg[i]));

21 } 22

23 printf("\n");

24

25 return 0;

26 } 27

28 int lowercase(const char *msg) 29 {

30 int i = 0;

31

32 // BUG: \0 termination problems 33 for(i = 0; msg[i] != '\0'; i++) { 34 printf("%c", tolower(msg[i]));

35 } 36

37 printf("\n");

38

39 return 0;

40 } 41

42 int fail_on_purpose(const char *msg) 43 {

44 return 1;

45 }

There’s nothing fancy in there, although there are some bugs I’m leaving in on purpose to see if you’ve been paying attention. You’ll fix those later.

What we want to do is use the functions dlopen, dlsym, and dlclose to work with the above functions.

ex29.c

Click he re to vie w code imag e 1 #include <stdio.h>

2 #include "dbg.h"

3 #include <dlfcn.h>

4

5 typedef int (*lib_function) (const char *data);

6

7 int main(int argc, char *argv[]) 8 {

9 int rc = 0;

10 check(argc == 4, "USAGE: ex29 libex29.so function data");

11

12 char *lib_file = argv[1];

13 char *func_to_run = argv[2];

14 char *data = argv[3];

15

16 void *lib = dlopen(lib_file, RTLD_NOW);

17 check(lib != NULL, "Failed to open the library %s: %s", lib_file, 18 dlerror());

19

20 lib_function func = dlsym(lib, func_to_run);

21 check(func != NULL,

22 "Did not find %s function in the library %s: %s", func_to_run, 23 lib_file, dlerror());

24

25 rc = func(data);

26 check(rc == 0, "Function %s return %d for data: %s", func_to_run, 27 rc, data);

28

29 rc = dlclose(lib);

30 check(rc == 0, "Failed to close %s", lib_file);

31

32 return 0;

33

34 error:

35 return 1;

36 }

I’ll now break this down so you can see what’s going on in this small bit of useful code:

ex29.c:5 I’ll use this function pointer definition later to call functions in the library. This is nothing new, but make sure you understand what it’s doing.

ex29.c:17 After the usual setup for a small program, I use the dlopen function to load up the library that’s indicated by lib_file. This function returns a handle that we use later, which works a lot like opening a file.

ex29.c:18 If there’s an error, I do the usual check and exit, but notice at then end that I’m using dlerror to find out what the library-related error was.

ex29.c:20 I use dlsym to get a function out of the lib by its string name in func_to_run. This is the powerful part, since I’m dynamically getting a pointer to a function based on a string I got from the command line argv.

ex29.c:23 I then call the func function that was returned, and check its return value.

ex29.c:26 Finally, I close the library up just like I would a file. Usually, you keep these open the whole time the program is running, so closing it at the end isn’t as useful, but I’m

demonstrating it here.

What You Should See

Now that you know what this file does, here’s a shell session of me building the libex29.so, ex29 and then working with it. Follow along so you learn how these things are manually built.

Exercise 29 Session

Click he re to vie w code imag e

# compile the lib file and make the .so

# you may need -fPIC here on some platforms. add that if you get an error

$ cc -c libex29.c -o libex29.o

$ cc -shared -o libex29.so libex29.o

# make the loader program

$ cc -Wall -g -DNDEBUG ex29.c -ldl -o ex29

# try it out with some things that work

$ ex29 ./libex29.so print_a_message "hello there"

-bash: ex29: command not found

$ ./ex29 ./libex29.so print_a_message "hello there"

A STRING: hello there

$ ./ex29 ./libex29.so uppercase "hello there"

HELLO THERE

$ ./ex29 ./libex29.so lowercase "HELLO tHeRe"

hello there

$ ./ex29 ./libex29.so fail_on_purpose "i fail"

[ERROR] (ex29.c:23: errno: None) Function fail_on_purpose return 1 for\

data: i fail

# try to give it bad args

$ ./ex29 ./libex29.so fail_on_purpose

[ERROR] (ex29.c:11: errno: None) USAGE: ex29 libex29.so function data

# try calling a function that is not there

$ ./ex29 ./libex29.so adfasfasdf asdfadff

[ERROR] (ex29.c:20: errno: None) Did not find adfasfasdf

function in the library libex29.so: dlsym(0x1076009b0, adfasfasdf):\

symbol not found

# try loading a .so that is not there

$ ./ex29 ./libex.so adfasfasdf asdfadfas

[ERROR] (ex29.c:17: errno: No such file or directory) Failed to open the library libex.so: dlopen(libex.so, 2): image not found

$

One thing that you may run into is that every OS, every version of every OS, and every compiler on every version of every OS, seems to want to change the way you build a shared library every time some new programmer thinks it’s wrong. If the line I use to make the libex29.so file is wrong, then let me know and I’ll add some comments for other platforms.

Warning!

Sometimes you’ll do what you think is normal, and run this command cc -Wall -g -DNDEBUG -ldl ex29.c -o ex29 thinking everything will work, but nope. You see, on some platforms the order of where libraries go makes them work or not, and for no real

reason. In Debian or Ubuntu, you have to do cc -Wall -g -DNDEBUG ex29.c -ldl -o ex29 for no reason at all. It’s just the way it is. So since this works on OS X I’m doing it here, but in the future, if you link against a dynamic library and it can’t find a function, try shuffling things around.

The irritation here is there’s an actual platform difference on nothing more than the order of command line arguments. On no rational planet should putting an -ldl at one position be different from another. It’s an option, and having to know these things is incredibly annoying.

How to Break It

Open libex29.so and edit it with an editor that can handle binary files. Change a couple of bytes, then close itlibex29.so. Try to see if you can get the dlopen function to load it even though you’ve corrupted it.

Extra Credit

• Were you paying attention to the bad code I have in the libex29.c functions? Do you see how, even though I use a for-loop they still check for '\0' endings? Fix this so that the functions always take a length for the string to work with inside the function.

• Take the c-skeleton skeleton, and create a new project for this exercise. Put the libex29.c file in the src/ directory. Change the Makefile so that it builds this as build/libex29.so.

• Take the ex29.c file and put it in tests/ex29_tests.c so that it runs as a unit test. Make this all work, which means that you’ll have to change it so that it loads the build/

libex29.so file and runs tests similar to what I did manually above.

• Read the man dlopen documentation and read about all of the related functions. Try some of the other options to dlopen beside RTLD_NOW.

Exercise 30. Automated Testing

Automated testing is used frequently in other languages like Python and Ruby, but rarely used in C.

Part of the reason comes from the difficulty of automatically loading and testing pieces of C code. In this chapter, we’ll create a very small testing framework and get your skeleton directory to build an example test case.

The framework I’m going to use, and you’ll include in your c-skeleton skeleton, is called minunit which started with a tiny snippet of code by Jera Design. I evolved it further, to be this:

minunit.h

Click he re to vie w code imag e 1 #undef NDEBUG 2 #ifndef _minunit_h 3 #define _minunit_h 4

5 #include <stdio.h>

6 #include <dbg.h>

7 #include <stdlib.h>

8

9 #define mu_suite_start() char *message = NULL 10

11 #define mu_assert(test, message) if (!(test)) {\

12 log_err(message); return message; }

13 #define mu_run_test(test) debug("\n---%s", " " #test); \ 14 message = test(); tests_run++; if (message) return message;

15

16 #define RUN_TESTS(name) int main(int argc, char *argv[]) {\

17 argc = 1; \

18 debug("--- RUNNING: %s", argv[0]);\

19 printf("----\nRUNNING: %s\n", argv[0]);\

20 char *result = name();\

21 if (result != 0) {\

22 printf("FAILED: %s\n", result);\

23 }\

24 else {\

25 printf("ALL TESTS PASSED\n");\

26 }\

27 printf("Tests run: %d\n", tests_run);\

28 exit(result != 0);\

29 } 30

31 int tests_run;

32

33 #endif

There’s practically nothing left of the original, since now I’m using the dbg.h macros and a large macro that I created at the end for the boilerplate test runner. Even with this tiny amount of code, we’ll create a fully functioning unit test system that you can use in your C code once it’s combined with a shell script to run the tests.

In document Learn C the Hard Way (Page 143-149)