Skip to main content dsal3389

Shared C Libraries

quick access to some sections

when we compile C code, we using shared libraries all the time without knowing it, to see what shared libraries some executable is using we can use the ldd command like so

sh code snippet start

> ldd <executable>
> ldd a.out
	linux-vdso.so.1 (0x...)
	libc.so.6 => /usr/lib/libc.so.6 (0x...)
	/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x...)

sh code snippet end

but what is a shared library?

a shared library is exactly how it sounds like, its a library that is shared between multiple programs, the library is loaded once into memory (as a read only block) and its shared between process that need to use it, there are couple of advantages:

  • executables are smaller size because the library is dynamiclly linked to the program and its not part of the executable
  • if we need to update something in the library we dont need to recompile the whole program, only the library and it will effect all programs that are using it
  • common libraries are loaded once into memory and shared between many process who need it

how this black magic is done?

the dynamic linker is the one who is responsible to know where the required shared libraries sit, and link your program with them dynamiclly when your program is loaded into memory

where the shared libraries are stored?

there are standard that tell us where to store them (look here) but we dont need to conver it right now, the dynamic linker read the contents of /etc/ld.so.conf, this file contain the path that tells the dynamic linker, where to search for the required shared library, most of the time it will be stored in

  • /usr/local/lib
  • /usr/lib

if we ls one of those folders we can see a lot of files

sh code snippet start

> ls /usr/lib
ld-linux-x86-64.so.2                    libbd_utils.so                            libicui18n.so.71.1                      libsoxr.so
ld-linux.so.2                           libbd_utils.so.2                          libicuio.so                             libsoxr.so.0
ldscripts                               libbd_utils.so.2.1.0                      libicuio.so.71                          libsoxr.so.0.1.2
libBrokenLocale.a                       libbd_vdo.so                              libicuio.so.71.1                        libspeex.so
libBrokenLocale.so                      libbd_vdo.so.2                            libicutest.so                           libspeex.so.1
libBrokenLocale.so.1                    libbd_vdo.so.2.0.0                        libicutest.so.71                        libspeex.so.1.5.1
...

sh code snippet end

please notic that all the files start with lib.so, this name format is requirement for shared libraries, we will comeback to it later

using shared libraries

when you compile your C program, you using some shared library commonly: libc or glibc, those shared libraries provide your C standard functionality, like string, stdio, stdarg, stdlib and so on

sometimes you need more then the standard library, you need encryption support for example, you can use the -l compiler flag to tell the dynamic linker to link another shared library for my program

for example take the next program, it takes a string of Hello world, and create sha1 for that string

c code snippet start

#include <stdio.h>
#include <openssl/sha.h>


void printf_hash(const unsigned char *hash){
	int i = 0;

	while(i<SHA_DIGEST_LENGTH){
		printf("%02x", hash[i]);
		i++;
	}
}

int main(int argc, char *argv[]){
	SHA_CTX ctx;
	char msg[] = "Hello world";
	unsigned char hash[SHA_DIGEST_LENGTH+1];

	SHA1_Init(&ctx);
	SHA1_Update(&ctx, msg, sizeof(msg) - 1);
	SHA1_Final(hash, &ctx);

	printf_hash(hash);
	putchar('\n');
}

c code snippet end

notice that we #include<openssl/sha.h>, if we now try to compile it, we will get an error

sh code snippet start

> gcc main.c 
/usr/bin/ld: /tmp/ccrD59Yr.o: in function `main':
main.c:(.text+0x93): undefined reference to `SHA1_Init'
/usr/bin/ld: main.c:(.text+0xae): undefined reference to `SHA1_Update'
/usr/bin/ld: main.c:(.text+0xc4): undefined reference to `SHA1_Final'
collect2: error: ld returned 1 exit status

sh code snippet end

the openssl is not part of the standard library, so those functions are not defined in libc, we need to tell the linker what libraries we need for that program

sh code snippet start

> gcc -lssl -lcrypto main.c

sh code snippet end

notice that we can use more then one shared library, we can see our executable shared libraries with the ldd command

sh code snippet start

> ldd a.out 
	linux-vdso.so.1 (0x...)
	libssl.so.1.1 => /usr/lib/libssl.so.1.1 (0x...)
	libcrypto.so.1.1 => /usr/lib/libcrypto.so.1.1 (0x...)
	libc.so.6 => /usr/lib/libc.so.6 (0x...)
	/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x...)

sh code snippet end

now we can run the program

sh code snippet start

> ./a.out 
7b502c3a1f48c8609ae212cdfb639dee39673f5e

> echo -n "Hello world" | sha1sum
7b502c3a1f48c8609ae212cdfb639dee39673f5e  -

sh code snippet end

write your own shared library

writing shared library is a very simple process, the heavy lifting is done by the dynamic linker, our development structure will look like so

code snippet start

|-- customlib
|   |-- mylib.c
|   `-- mylib.h
`-- src
    `-- main.c

code snippet end

our mylib.c contain some simple functionality, first lets see the content of mylib.h

c code snippet start

// mylib.h

#ifndef _MYLIB_H_
#define _MYLIB_H_

#ifndef NAME_BUFFER_SIZE
	#define NAME_BUFFER_SIZE 64
#endif

int set_name(const char *);
void say(const char *);

#endif

c code snippet end

now lets inspect our mylib.c source code

c code snippet start

#include <stdio.h>
#include <string.h>
#include "mylib.h"


static char name[NAME_BUFFER_SIZE] = "unknown";


int set_name(const char *nname){
	if(strlen(nname) >= sizeof(name)){
		return -1;
	}
	strcpy(name, nname);
	return 0;
}

void say(const char *msg){
	printf("%s: %s", name, msg);
}

c code snippet end

as we can see, it is a very simple library, it has static buffer called name, it have 2 functions, set_name and say, lets compile our code, importent note: when creating shared library, the library name must be in the next format lib<name>.so

sh code snippet start

> gcc -shared -fpic mylib.c -o libmylib.so

sh code snippet end


flags description from gcc man page

-shared: “Produce a shared object which can then be linked with other objects to form an executable …”

-fpic: “Generate position-independent code (PIC) suitable for use in a shared library …”


the output file that shoult we get is:

sh code snippet start

> ls
libmylib.so  mylib.c  mylib.h

> file libmylib.so 
libmylib.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=..., with debug_info, not stripped
.

sh code snippet end

now lets use this shared library in our program

c code snippet start

// main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mylib.h>


int main(int argc, char *argv[]){
	if(argc < 2 || strcmp(argv[1], "--help") == 0){
		fprintf(stderr, "usage: %s [name]\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	if(set_name(argv[1]) != 0){
		fprintf(stderr, "given name was too long (%s)\n", argv[1]);
		exit(EXIT_FAILURE);
	}

	say("Hello world\n");
}

c code snippet end

(please note that we included our library with <>, we can also use “”, but because that lib is not local to our program, it is more correct to use <>)

to compile our program we need to inform the linker where to search for our custom lib, the most simple way is to provide -I and -L flags like so

sh code snippet start

> gcc -I../customlib -L../customlib -lmylib main.c

sh code snippet end


flags description from gnu man page

-L: “Add directory dir to the list of directories to be searched for -l.”

-I: “Any directories specified with -I options before -I- are searched only for headers requested with “#include “file””; they are not searched for “#include ”. If additional directories are specified with -I options after the -I-, those directories are searched for all #include directives.”


we can inspect our executable with nm and ldd for the dynamic symbols and shared libraries

sh code snippet start

> nm -D ./a.out
...
                 U exit@GLIBC_2.2.5
                 U fprintf@GLIBC_2.2.5
                 U say
                 U set_name
...

> ldd ./a.out
	linux-vdso.so.1 (0x...)
	libmylib.so => not found
	libc.so.6 => /usr/lib/libc.so.6 (0x...)
	/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x...)

sh code snippet end

with nm we can see that our functions (say and set_name) are undefined (U), so they should be loaded at load time by the dynamic linker, but with ldd we can see that the dynamic linker dont know where libmylib.so is located, because it couldn’t find it in the paths specified in /etc/ld.so.conf, it is not recommended to change the content of that file and add paths, so the correct way inform the linker about our shared library location is with environment variable

LD_LIBRARY_PATH

this environment variable allow us to specify paths to the dynamic linker without editing to contents of /etc/ld.so.conf, each path is seperated with : like normat $PATH environment variable, we should define that environment variable like so

sh code snippet start

> export LD_LIBRARY_PATH=../customlib:$LD_LIBRARY_PATH
> ./a.out
usage: ./a.out [name]

# alternative, allow the environ variable only for a specific program environment

> LD_LIBRARY_PATH=../customlib:$LD_LIBRARY_PATH ./a.out
usage: ./a.out [name]

sh code snippet end

run the program

now we can run our program with that environment variable set

sh code snippet start

> LD_LIBRARY_PATH=../customlib:$LD_LIBRARY_PATH ./a.out foo
foo: Hello world

sh code snippet end