Library Calling in C

4 minute read

Published:

In this post we are exploring how different type of binaries are generated and how they work.

Note

Difference in type of files:

.a are archive files.
.o are object files
.so are shared libraries files.


Setup

We have out simple main.cpp file

mainimage

and we have a small library called lib.cpp,

mainimage

As can be seen, there is no main function present in the lib.c file and hence we cannot compile it using simple

gcc lib.c

This will result into an error, saying that main is not present.

mainimage

We want it to compile using the command

gcc -c lib.c -o lib.o

which tell the compiler to skip the linking part, as it is during the linking part it is not able to find an entry point in the program. Using the option -c results into different kind of files, as can been seen in the type reported by linux using the command file

mainimage

Our lib.o is a non-executable object file, which can be relocated (basically means that it can be place in the memory anywhere). Our main.o is a dynamically linked executable.

Our aim is to call this function, out_hello which is present in the lib.o from main.o. However, before doing that, we will explored a tool called GDB to explore our object files a little more.

GDB tools

A complete tutorial can be found here https://www.tutorialspoint.com/gnu_debugger/index.htm

The important point is to compile the binary using the -g parameter so that the debug symbols are present in the binary.

g++ main.cpp -g -o main.o

As, can be seen in the main.cpp

   // memory map
    struct stat filestat;

	if(fstat(fd, &filestat) !=0) {
	   perror("stat failed");
	   exit(1);
	}

  	void *memory;
	memory = mmap(NULL, filestat.st_size, PROT_READ, MAP_SHARED, fd, 0);
	if(memory == MAP_FAILED) {
	   perror("mmap failed");
	   exit(2);
	}else{
		mprotect(memory, filestat.st_size, PROT_EXEC);
	}
	printf("Data address is %x\n", memory);

The code loads lib.o in the memory using mmap and then marks it as protected so that it can be executed. Due to security measures to prevent code injection attacks, an executable memory has to be marked read-only or protected before it can be executed.

Using GDB, we will let the program execute till the last printf statement which for me is line number 36, halt the code there and explore the list of the functions available.

gdb
file main.o
b main.cpp:36
info proc mappings

The mappings that are shown are:

Start AddrEnd AddrSizeOffset objfile
0x5555555540000x5555555560000x20000x0 /home/sandeep/Desktop/intel_sgx/test_binary_call/main.o
0x5555557550000x5555557560000x10000x1000 /home/sandeep/Desktop/intel_sgx/test_binary_call/main.o
0x5555557560000x5555557570000x10000x2000 /home/sandeep/Desktop/intel_sgx/test_binary_call/main.o
0x5555557570000x5555557780000x210000x0 [heap]
0x7ffff70a50000x7ffff70bc0000x170000x0 /lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff70bc0000x7ffff72bb0000x1ff0000x17000 /lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff72bb0000x7ffff72bc0000x10000x16000 /lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff72bc0000x7ffff72bd0000x10000x17000 /lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff72bd0000x7ffff745a0000x19d0000x0 /lib/x86_64-linux-gnu/libm-2.27.so
0x7ffff745a0000x7ffff76590000x1ff0000x19d000 /lib/x86_64-linux-gnu/libm-2.27.so
0x7ffff76590000x7ffff765a0000x10000x19c000 /lib/x86_64-linux-gnu/libm-2.27.so
0x7ffff765a0000x7ffff765b0000x10000x19d000 /lib/x86_64-linux-gnu/libm-2.27.so
0x7ffff765b0000x7ffff78420000x1e70000x0 /lib/x86_64-linux-gnu/libc-2.27.so
0x7ffff78420000x7ffff7a420000x2000000x1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7ffff7a420000x7ffff7a460000x40000x1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7ffff7a460000x7ffff7a480000x20000x1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7ffff7a480000x7ffff7a4c0000x40000x0
0x7ffff7a4c0000x7ffff7bc50000x1790000x0 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
0x7ffff7bc50000x7ffff7dc50000x2000000x179000 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
0x7ffff7dc50000x7ffff7dcf0000xa0000x179000 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
0x7ffff7dcf0000x7ffff7dd10000x20000x183000 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
0x7ffff7dd10000x7ffff7dd50000x40000x0
0x7ffff7dd50000x7ffff7dfc0000x270000x0 /lib/x86_64-linux-gnu/ld-2.27.so
0x7ffff7fcb0000x7ffff7fd10000x60000x0
0x7ffff7ff60000x7ffff7ff70000x10000x0 /home/sandeep/Desktop/intel_sgx/test_binary_call/lib.o
0x7ffff7ff70000x7ffff7ffa0000x30000x0 [vvar]
0x7ffff7ffa0000x7ffff7ffc0000x20000x0 [vdso]
0x7ffff7ffc0000x7ffff7ffd0000x10000x27000 /lib/x86_64-linux-gnu/ld-2.27.so
0x7ffff7ffd0000x7ffff7ffe0000x10000x28000 /lib/x86_64-linux-gnu/ld-2.27.so
0x7ffff7ffe0000x7ffff7fff0000x10000x0
0x7ffffffdd0000x7ffffffff0000x220000x0 [stack]
0xffffffffff600000 0xffffffffff6010000x10000x0 [vsyscall] 

We can see that our lib.o is a mapped function in the binary.

This can can be combined in a single line:
gdb --batch --command=test.gdb

Calling the function

Now, we have mapped it. We need to call this. That is little tricky and there are multiple ways to do that:

  • Option 1
    Link the library with the binary and create a single object file which is capable of running and callig the functions present in the library.

  • Option 2
    In this case the main function and the library functions remains in separate file. The main can call the function required for its operation.

Option 1: Statically compiled libraries

The basic idea here is to call a function defined in some other binary(acting as a libary) from our sample application.


gcc -c example.c -o example.o  
gcc -c lib.c -fPIC -o lib.o  
ar rcs libslib.a lib.o  
gcc example.o -L. -lslib -o sexample.o  
./sexample.o   


Option 2: Dynamically calling the library

This is done via using LD_PRELOAD option.