Linux executables and so files?

So, I'm totally self-taught with basically everything computers. I've switched from Windows to Linux less than a year ago and I'm in love with it. Now, I'm having some trouble understanding how to link a .so file to my executable.

so.cpp
1
2
3
4
5
#include<cstdio>

void sexy_talk(){
printf("Insert some magical dirty talk here. ");
}


main.cpp
1
2
3
4
5
6
7
8
9
#include <dlfcn.h>

int main(){

void *Handlr = dlopen("exec.so",RTLD_LAZY);
void *func = dlsym(Handlr,"dirty_talk");

func();
}


Of course, this whole world of Linux executables is new to me, even the ELF format. It seems to me, but I'm not too sure, why it seems like the so file is smaller than if I were to compile a standard executable, and why the .o file is so tiny it's almost cute. Why is it so small?? What is the .so carrying that the .o isn't?

Also, please don't call .so libraries, since it's a synonym of a thousand other computing terms. It confuses me.

Also, forgot to mention, what's wrong with my code? It says "main.cpp:8:15: error: ‘func’ cannot be used as a function", though I'm told that func becomes a function.
Last edited on
The ELFormat is a rather comprehensive format that can contain a rather large list of different types of binaries.

EDIT: It seems the more savvy people with the ELF format call what I called object file below, relocatable file. I've adjusted it below but most people are still going to call them object files. In addition, object files in strict terms can mean all three of the main binary forms (executable, shared object, and relocatable).

".so" extension generally refers to shared object. ".o" extension just means relocatable file.

As for differences between executable and shared object, there aren't many. A shared object can be used as an executable even (try executing libc.so.6 for instance). But an executable can't be used as a shared object because it lacks the information required in order to link against it. Unfortunately, my knowledge somewhat tapers off here.

The same goes for an relocatable file and shared object. They use the same format. The difference is their purpose and what information they contain. An relocatable file is usually generated from some compiler that generates native machine code. As far as I know, if it's something other than x86/amd64, it's not the ELF format they're using. You're not going to (generally) link directly against an relocatable file and it lacks the information to do so, leaving that job for your system linker (usually ld.gold nowadays).

A shared object or executable is generally created by various relocatable files. There's various reasons for this, one being that the relocatable file doesn't care about what language you're using. You can link relocatable files from Rust, D, C, and C++ together without issues.

Your code is wrong because of the type of "func". You have to cast func to the signature it was implemented as. As it is now, it's just a void pointer and not callable.
Last edited on
Thanks for the explanation, it really helped.

I just realised I messed up the function name so I changed that but it still doesn't work. I don't understand what you mean by "You have to cast func to the signature it was implemented as.", could you explain further? What do you mean by "signature"?

EDIT:
I've made a few improvements:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <dlfcn.h>
typedef void(*fptr)();

fptr func;

int main(){

void* Handlr = dlopen("./so.so",RTLD_LAZY);

func = (fptr)dlsym(Handlr,"sexy_talk");

func();
}


But now I get a segfault when it runs. The segfault occurs when func() is run.
Last edited on
Cast .. read the example from
man dlsym


You are linking the shared object explicitly during runtime. The advantage is that your executable can handle errors (like .so not present).

The more common usage of shared objects is implicit linking. You tell linker to include your libso.so, and then dynamic linker will do the loading and mapping. No pointers, no casts, but the .so must be found or the executable won't start.
I've read the man page, and while it's cleared up a few terms, I still don't know what casting or a signature is. I understand that the method that I'm trying to learn is a little more complicated but it's the one that I want to learn.

Why would the new function, when executed, give a segfault? The .so is present and everything seems fine.

EDIT: Thankfully, my debugger has blessed me by telling me

./so.so: undefined symbol: sexy_talk1
Segmentation fault (core dumped)


But the symbol is declared as sexy_talk, not sexy_talk1, right? I hope that helps?
Last edited on
I'd have to see the code you're working with.

In the generally used C ABI, the symbol that would appear in your library is the same as your function name. There are some cases where this may differ... you'd have to see the different calling conventions. For instance, __fastcall in Visual C++ will prepend an '@' character to the beginning of the symbol.

A cast is nothing but a language semantic. It doesn't change the data in anyway (at least not in C). It's necessary in order to understand what you're calling though. When you call func() above, It doesn't make sense to call a void pointer. It has to be a function. A function in C is nothing more than a pointer somewhere that's defined to be something that's callable. In this case, a (void*) is not callable. It doesn't define a return type, arguments, or even that it's a function. If you have a void return function with no arguments, you would cast the void pointer to "void(*)();"

A signature is just a word that describes the type of function that it is. It's synonymous to "the definition of the function" or "function prototype". For an analogy, when you write your signature, it's unique to you. Each type has its own signature.

You need to understand what casting does and how types work. They're essential to every language practically and they'll only get more complicated as they become more powerful with time.
Last edited on
Also, C++ functions do something called 'name mangling', which is where you are getting your problem, most likely. After all, unlike in C, in C++ you can have identically named but different functions through function overloading or namespaces.

If you are happy to use the functions in a C-like manner, which basically just means only using C types in the definition of the function, you can declare the type as extern "C", like so:
1
2
3
4
extern "C"
void func(void) {
    // ...
}

You can also declare a bunch of functions to all have C linkage, like so:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifdef __cplusplus // this is a C++ file
extern "C" {
#endif

void foo(void) {
    // ...
}

void bar(void) {
    // ...
}

#ifdef __cplusplus
}
#endif 

I may have gotten the syntax slightly wrong, but that should give you an idea. Basically, this means that (with some calling conventions, at least) a function foo is just exported as foo, rather than _1foo@4 or something. Just make sure that you aren't exporting C++ (and hence, mangled) functions.
See, http://stackoverflow.com/questions/2322736/what-is-the-difference-between-function-declaration-and-signature
Particularly, the "Note: Signatures are used as a basis for name mangling and linking."

Try these:
nm so.so | grep sexy
nm -C so.so | grep sexy


The dlsym() does not support mangling because it has been written for C which does not use mangling.

You can add code to so.cpp that makes compiler/linker treat sexy_talk() as C-function and then exposes unmangled symbol "sexy_talk".


Casting is type conversion.
The dlsym() returns a pointer (to function), but the actual type cannot be included. Therefore, a pointer to void is returned. Every pointer type can be implicitly converted to void*. The dlsym() does so. It has to.

C/C++ has strict typing and you cannot use a pointer variable as a function unless the variable has a correct type. Your explicit cast states that the pointer to void returned by dlsym() is actually a function pointer and that function takes no arguments and returns no value.


segfault
You don't test whether the dlopen succeeds.
You don't test whether the dlsym succeeds.
Without testing you cannot know whether the func is a valid pointer.
Dereferencing invalid pointer does yield segfault, if you are lucky.
Last edited on
Awesome, awesome, awesome. I didn't test in the code that I posted, but I did post later on which revealed that the symbol was mangled (which I didn't know at the time.) It turns out, prepending extern "C" worked out, thanks for filling me in and thanks for NoXzema for explaining a lot of things (even stuff I already knew).

Topic archived. No new replies allowed.