Library Calling in C
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
and we have a small library called lib.cpp,
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.
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
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 Addr | End Addr | Size | Offset objfile |
---|---|---|---|
0x555555554000 | 0x555555556000 | 0x2000 | 0x0 /home/sandeep/Desktop/intel_sgx/test_binary_call/main.o |
0x555555755000 | 0x555555756000 | 0x1000 | 0x1000 /home/sandeep/Desktop/intel_sgx/test_binary_call/main.o |
0x555555756000 | 0x555555757000 | 0x1000 | 0x2000 /home/sandeep/Desktop/intel_sgx/test_binary_call/main.o |
0x555555757000 | 0x555555778000 | 0x21000 | 0x0 [heap] |
0x7ffff70a5000 | 0x7ffff70bc000 | 0x17000 | 0x0 /lib/x86_64-linux-gnu/libgcc_s.so.1 |
0x7ffff70bc000 | 0x7ffff72bb000 | 0x1ff000 | 0x17000 /lib/x86_64-linux-gnu/libgcc_s.so.1 |
0x7ffff72bb000 | 0x7ffff72bc000 | 0x1000 | 0x16000 /lib/x86_64-linux-gnu/libgcc_s.so.1 |
0x7ffff72bc000 | 0x7ffff72bd000 | 0x1000 | 0x17000 /lib/x86_64-linux-gnu/libgcc_s.so.1 |
0x7ffff72bd000 | 0x7ffff745a000 | 0x19d000 | 0x0 /lib/x86_64-linux-gnu/libm-2.27.so |
0x7ffff745a000 | 0x7ffff7659000 | 0x1ff000 | 0x19d000 /lib/x86_64-linux-gnu/libm-2.27.so |
0x7ffff7659000 | 0x7ffff765a000 | 0x1000 | 0x19c000 /lib/x86_64-linux-gnu/libm-2.27.so |
0x7ffff765a000 | 0x7ffff765b000 | 0x1000 | 0x19d000 /lib/x86_64-linux-gnu/libm-2.27.so |
0x7ffff765b000 | 0x7ffff7842000 | 0x1e7000 | 0x0 /lib/x86_64-linux-gnu/libc-2.27.so |
0x7ffff7842000 | 0x7ffff7a42000 | 0x200000 | 0x1e7000 /lib/x86_64-linux-gnu/libc-2.27.so |
0x7ffff7a42000 | 0x7ffff7a46000 | 0x4000 | 0x1e7000 /lib/x86_64-linux-gnu/libc-2.27.so |
0x7ffff7a46000 | 0x7ffff7a48000 | 0x2000 | 0x1eb000 /lib/x86_64-linux-gnu/libc-2.27.so |
0x7ffff7a48000 | 0x7ffff7a4c000 | 0x4000 | 0x0 |
0x7ffff7a4c000 | 0x7ffff7bc5000 | 0x179000 | 0x0 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 |
0x7ffff7bc5000 | 0x7ffff7dc5000 | 0x200000 | 0x179000 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 |
0x7ffff7dc5000 | 0x7ffff7dcf000 | 0xa000 | 0x179000 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 |
0x7ffff7dcf000 | 0x7ffff7dd1000 | 0x2000 | 0x183000 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25 |
0x7ffff7dd1000 | 0x7ffff7dd5000 | 0x4000 | 0x0 |
0x7ffff7dd5000 | 0x7ffff7dfc000 | 0x27000 | 0x0 /lib/x86_64-linux-gnu/ld-2.27.so |
0x7ffff7fcb000 | 0x7ffff7fd1000 | 0x6000 | 0x0 |
0x7ffff7ff6000 | 0x7ffff7ff7000 | 0x1000 | 0x0 /home/sandeep/Desktop/intel_sgx/test_binary_call/lib.o |
0x7ffff7ff7000 | 0x7ffff7ffa000 | 0x3000 | 0x0 [vvar] |
0x7ffff7ffa000 | 0x7ffff7ffc000 | 0x2000 | 0x0 [vdso] |
0x7ffff7ffc000 | 0x7ffff7ffd000 | 0x1000 | 0x27000 /lib/x86_64-linux-gnu/ld-2.27.so |
0x7ffff7ffd000 | 0x7ffff7ffe000 | 0x1000 | 0x28000 /lib/x86_64-linux-gnu/ld-2.27.so |
0x7ffff7ffe000 | 0x7ffff7fff000 | 0x1000 | 0x0 |
0x7ffffffdd000 | 0x7ffffffff000 | 0x22000 | 0x0 [stack] |
0xffffffffff600000 0xffffffffff601000 | 0x1000 | 0x0 [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.