Compiling module for lua with gcc - c++

I have the following c++ code for testing:
#include <lua.hpp>
#include <iostream>
static int dummy(lua_State * L)
{
std::cout << "Test";
return 0;
}
int luaopen_testlib(lua_State * L)
{
lua_register(L,"dummy",dummy);
return 0;
}
I compile it with commands and it gives me no errors:
g++ -Wextra -O2 -c -o testlib.o main.cpp
g++ -shared -o testlib.so testlib.o
But when i try to load it in lua i get undefined symbol error as this:
Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio
> require"testlib"
error loading module 'testlib' from file './testlib.so':
./testlib.so: undefined symbol: _Z16lua_pushcclosureP9lua_StatePFiS0_Ei
It seems for me that there is something missing in the g++ commands, but i have been searching solution for whole morning and can't get this simple example to compile.
EDIT:
after few recompilations it returned to:
error loading module 'testlib' from file './testlib.so':
./testlib.so: undefined symbol: luaopen_testlib
which was solved by adding :
extern "C"
{
int luaopen_testlib(lua_State *L)
{
lua_register(L,"dummy",dummy);
return 0;
}
}

The Lua binary is compiled as C code, the library tries to use it as C++. That will not work as C++ does name mangling to support overloading. As C does not support overloading it does not need the name mangling and will not understand mangled names.
The solution to this is to tell the C++ compiler that the Lua functions it is going to interact with are straight C and they need no name mangling.
Also the luaopen_testlib function must be extern "C" as it will be called from C code with no mangling.
extern "C" {
#include <lua.h>
}
#include <iostream>
static int dummy(lua_State * L)
{
(void)L;
std::cout << "Test"<<std::endl;
return 0;
}
extern "C"
int luaopen_testlib(lua_State * L)
{
lua_register(L,"dummy",dummy);
return 0;
}
I ran my test with Lua 5.4.2 and used the following commands to build the library:
g++ -Wall -Wextra -O2 -Isrc -c -fPIC -o testlib.o testlib.cpp
g++ -shared -o testlib.so testlib.o
Note the -Isrc is needed to find lua.h in my test setup and -fPIC was required to use cout in the library (but that may depend on the compiler version used).
and the result is:
Lua 5.4.2 Copyright (C) 1994-2020 Lua.org, PUC-Rio
> require 'testlib'
true ./testlib.so
> dummy
function: 0x7ff07d2a0aa0
> dummy()
Test
>
The Lua version used will, in this case, not make any difference.

Try to use Luabind. Here is the Hello World example
#include <iostream>
#include <luabind/luabind.hpp>
void greet()
{
std::cout << "hello world!\n";
}
extern "C" int init(lua_State* L)
{
using namespace luabind;
open(L);
module(L)
[
def("greet", &greet)
];
return 0;
}

This is how i would compile lua, is not exacly gcc but cmake can use gcc.
.
├── CMakeList.txt (A)
├── main.cpp
├── lua_535
│ ├── CMakeLists.txt (B)
│ └── * lua_content *
main.cpp | Just checks if it works
#include <iostream>
#include <string>
#include "lua.hpp"
int main(){
lua_State * lua = luaL_newstate();
std::string str_acction = "a = 5";
int res = luaL_dostring(lua, str_acction.c_str());
std::cout << "DS State > " << res << std::endl;
lua_close(lua);
return 0;
}
CMakeList.txt (A) | Creates the executable and links the library
cmake_minimum_required(VERSION 3.12)
project(lua_test)
add_executable(main main.cpp)
add_subdirectory("lua_535")
target_link_libraries(main PUBLIC lua_lib)
CMakeList.txt (B) | Joins Lua files into a library
Get the latest lua source files
Extract the content into a sub folder
Add this file into the folder
cmake_minimum_required(VERSION 3.12)
project( lua_lib )
set ( LUA_EMBEDDED ON )
set ( LUA_RUNTIME_MAIN "src/luac.c" )
set (LUA_RUNTIME_SOURCES
"src/lapi.c"
"src/lapi.h"
"src/lauxlib.c"
"src/lauxlib.h"
"src/lbaselib.c"
"src/lbitlib.c"
"src/lcode.c"
"src/lcode.h"
"src/lcorolib.c"
"src/lctype.c"
"src/lctype.h"
"src/ldblib.c"
"src/ldebug.c"
"src/ldebug.h"
"src/ldo.c"
"src/ldo.h"
"src/ldump.c"
"src/lfunc.c"
"src/lfunc.h"
"src/lgc.c"
"src/lgc.h"
"src/linit.c"
"src/liolib.c"
"src/llex.c"
"src/llex.h"
"src/llimits.h"
"src/lmathlib.c"
"src/lmem.c"
"src/lmem.h"
"src/loadlib.c"
"src/lobject.c"
"src/lobject.h"
"src/lopcodes.c"
"src/lopcodes.h"
"src/loslib.c"
"src/lparser.c"
"src/lparser.h"
"src/lprefix.h"
"src/lstate.c"
"src/lstate.h"
"src/lstring.c"
"src/lstring.h"
"src/lstrlib.c"
"src/ltable.c"
"src/ltable.h"
"src/ltablib.c"
"src/ltm.c"
"src/ltm.h"
"src/lua.c"
"src/lua.h"
"src/lua.hpp"
"src/luaconf.h"
"src/lualib.h"
"src/lundump.c"
"src/lundump.h"
"src/lutf8lib.c"
"src/lvm.c"
"src/lvm.h"
"src/lzio.c"
"src/lzio.h"
)
add_library( lua_lib "${LUA_RUNTIME_SOURCES}" )
if( NOT LUA_EMBEDDED)
add_library( lua_lib "${LUA_RUNTIME_MAIN}")
endif()
target_include_directories ( lua_lib PUBLIC "${PROJECT_SOURCE_DIR}/src")
If lua is enbedded src/luac.c should be excluded because conteins a int main(){}

Related

Calling a shared library in C++ from Chromium results in ld.lld: error: undefined symbol:

Using C++, my goal is to call a function from my own shared library from within Chromium.
When I compile, I get ld.lld: error: undefined symbol: my_class::print_a_dot()
Can anyone suggest why I am getting undefined symbol? thanks!
ubuntu:~/chromium/src> autoninja -C out/Default chrome
ninja: Entering directory `out/Default'
[16888/17733] SOLINK ./libcontent.so
FAILED: libcontent.so libcontent.so.TOC
python3 "../../build/toolchain/gcc_solink_wrapper.py" --readelf="../../third_party/llvm-build/Release+Asserts/bin/llvm-readelf" --nm="../../third_party/llvm-build/Release+Asserts/bin/llvm-nm" --sofile="./libcontent.so" --tocfile="./libcontent.so.TOC" --output="./libcontent.so" -- ../../third_party/llvm-build/Release+Asserts/bin/clang++ -shared -Wl,-soname="libcontent.so" -Werror -fuse-ld=lld -Wl,--fatal-warnings -Wl,--build-id -fPIC -Wl,-z,noexecstack -Wl,-z,relro -Wl,--color-diagnostics -Wl,--undefined-version -Wl,--no-call-graph-profile-sort -m64 -no-canonical-prefixes -Wl,--gdb-index -rdynamic -Wl,-z,defs -Wl,--as-needed -nostdlib++ --sysroot=../../build/linux/debian_bullseye_amd64-sysroot -Wl,-rpath=\$ORIGIN -o "./libcontent.so" #"./libcontent.so.rsp"
ld.lld: error: undefined symbol: my_class::print_a_dot()
>>> referenced by delegated_frame_host.cc:457 (../../content/browser/renderer_host/delegated_frame_host.cc:457)
>>> obj/content/browser/browser/delegated_frame_host.o:(content::DelegatedFrameHost::DidCopyStaleContent(std::Cr::unique_ptr<viz::CopyOutputResult, std::Cr::default_delete<viz::CopyOutputResult>>))
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
[16913/17733] CXX irt_x64/obj/ipc/mojom/ipc.mojom.o
ninja: build stopped: subcommand failed.
:ubuntu:~/chromium/src>
For my shared library, outside Chroimium I created a C++ source file my_class.cpp
ubuntu:~/learn> cat my_class.cpp
#include <stdio.h>
#include <iostream>
#include <memory>
#include <stdint.h>
#include "my_class.h"
extern void my_class::print_a_dot()
{
std::cout << ".";
}
For my shared library, outside Chromium I created a C++ header file my_class.h
:ubuntu:~/learn> cat my_class.h
#ifndef MY_CLASS_H // To make sure you don't declare the function more than once by including the header multiple times.
#define MY_CLASS_H
class my_class {
public:
static int some_number;
static bool initialised;
void print_a_dot();
int init_encoder();
};
#endif
For my shared library, outside Chromium I created a CMakeLists.txt
:ubuntu:~/learn> cat CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyProject)
add_library(myexample SHARED my_class.cpp)
:ubuntu:~/learn>
In the Chromium third_party directory named "dude4" I created a directory so Chromium can access my shared library:
third_party % find dude4
dude4
dude4/BUILD.gn
dude4/include
dude4/include/my_class.h
third_party %
The BUILD.gn contains:
config("my_class_import") {
include_dirs = ["include"]
libs = ["/home/ubuntu/learn/build/libmyexample.so"]
}
group("my_class") {
public_configs = [":my_class_import"]
}
I want to call my shared library from within Chromium, so I add the following lines at the bottom of the header lines in delegated_frame_host.cc
#include "third_party/dude4/include/my_class.h"
//using namespace N;
bool my_class::initialised = false;
int my_class::some_number = 999;
And also in delegated_frame_host.cc I add some code to call my shared library:
void DelegatedFrameHost::DidCopyStaleContent(
std::unique_ptr<viz::CopyOutputResult> result) {
// host may have become visible by the time the request to capture surface is
// completed.
my_class mc;
mc.initialised = false;
std::cout << "my_class::some_number: ";
std::cout << my_class::some_number << std::endl;
my_class::some_number = 888;
std::cout << "mc.some_number: ";
std::cout << mc.some_number << std::endl;
my_class nc;
std::cout << "nc.some_number: ";
std::cout << nc.some_number << std::endl;
if (mc.initialised == false) {
std::cout << "initialising encoder" << std::endl;
mc.initialised = true;
}
mc.print_a_dot();
(code of this function continues.......)

Force alle functions in shared library to be defined

I want to write a shared library and I want to get a compiler/linker error if I forgot to implement some functions.
Consider the following case:
test.h
class Test {
public:
Test();
};
test.cpp
#include "test.h"
main.cpp
#include "test.h"
int main() {
new Test();
}
If I create a library with this command gcc -c -fpic test.cpp && g++ -shared -o libtest.so -Wl,--no-undefined -Wl,--no-allow-shlib-undefined test.o there is no error message, but the library is broken. Is there a way to force the creation of a not broken library?
Edit: adding additional flag, but doesn't change result
These codes have been modified:
test.h :
class Test {
public:
Test();
};
test.cpp :
#include "test.h"
Test::Test(){} // you must implement the constructor
You must have to implement the constructor, and if not, you get an error "undefined reference to `Test::Test()'".
main.cpp :
#include <iostream>
#include "test.h"
using namespace std;
int main(void)
{
Test* t = new Test(); // you must define a pointer
cout << "test* was created: " << t << endl;
delete t;
t = nullptr;
return 0;
}
Now all the code is OK. Then we create a shared-library with the following command:
g++ -shared -o test.so -fPIC test.cpp
Finally, we compile the main.cpp file at the same time as referring to the test.so shared-library and get the exe output, by the command below:
g++ -g main.cpp test.so -o test.exe

How could I generate runnable shared library with cmake

I have seens some answers, but most of them does not work for me. So I would like to make everything clear by adding a new question, my CMakeLists.txt is like this:
cmake_minimum_required(VERSION 3.17)
project(example)
include_directories(./)
link_directories(./)
if (CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -Os -g ${CMAKE_CXX_FLAGS}")
message("CMAKE_COMPILER_IS_GNUCXX is True")
message("option is: ${CMAKE_CXX_FLAGS}")
endif (CMAKE_COMPILER_IS_GNUCXX)
add_executable(main try.cpp)
target_link_libraries(main fun)
set_property(TARGET main PROPERTY POSITION_INDEPENDENT_CODE 1 LINK_FLAGS -pie)
add_library(fun SHARED func.cpp)
# target_compile_options(fun PUBLIC "-pie")
target_link_libraries(fun PUBLIC "-pie")
set_property(TARGET fun PROPERTY POSITION_INDEPENDENT_CODE 1)
And the source code for try.cpp is:
#include<iostream>
#include "func.hpp"
int main() {
using namespace std;
cout << "hello from exe main" << endl;
func();
return 0;
}
The code for fun.cpp and fun.hpp is like this:
// func.hpp
void func();
// func.cpp
#include <iostream>
using std::cout;
using std::endl;
void func() {
cout << "hell from so func\n";
}
int main() {
cout << "hello from so main\n";
return 0;
}
My problem is that: I got the following link error when I compile it with cmake:
Scanning dependencies of target fun
[ 25%] Building CXX object CMakeFiles/fun.dir/func.cpp.o
[ 50%] Linking CXX shared library libfun.so
[ 50%] Built target fun
Scanning dependencies of target main
[ 75%] Building CXX object CMakeFiles/main.dir/try.cpp.o
[100%] Linking CXX executable main
/usr/bin/ld: CMakeFiles/main.dir/try.cpp.o: in function `main':
/home/coin/learn-coding/projects/C/cmake/try.cpp:8: undefined reference to `func()'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/main.dir/build.make:105: main] Error 1
make[1]: *** [CMakeFiles/Makefile2:125: CMakeFiles/main.dir/all] Error 2
make: *** [Makefile:104: all] Error 2
What is the problem with the configuration and how could I make it work ?
By the way, I tried to compile with command line:
g++ -fPIC -pie func.cpp -o libfun.so -shared
g++ try.cpp -o main -L. -lfun
Which does work when I run the generated main, but the generated so file cannot be runnable:
$ ./main
hello from exe main
hell from so func
$ ./libfun.so
Segmentation fault (core dumped)
What did I miss here ?
The following files based on this answer:
cat >CMakeLists.txt <<EOF
cmake_minimum_required(VERSION 3.17)
project(example)
if(CMAKE_COMPILER_IS_GNUCXX)
add_compile_options(
$<$<COMPILE_LANGUAGE:CXX>:-std=c++11>
-Wall
-Os
-g
)
endif()
add_executable(main try.cpp)
target_link_libraries(main PRIVATE fun)
add_library(fun SHARED func.cpp)
target_link_options(fun PRIVATE -Wl,-e,entry_point)
EOF
cat >func.cpp <<EOF
// func.hpp
void func();
// func.cpp
#include <iostream>
using std::cout;
using std::endl;
void func() {
cout << "hell from so func\n";
}
static inline
int lib_main() {
printf("hello from so main\n");
return 0;
}
extern "C" const char interp_section[] __attribute__((section(".interp"))) = "/lib64/ld-linux-x86-64.so.2";
extern "C" void entry_point()
{
lib_main();
exit(0);
}
EOF
cat >try.cpp <<EOF
#include<iostream>
void func();
int main() {
using namespace std;
cout << "hello from exe main" << endl;
func();
return 0;
}
EOF
when compiled with the following on my 64-bit system with glibc, it generates two files that are executable:
$ cmake -S . -B _build && cmake --build _build -- VERBOSE=1
balbla compilation output
$ _build/main
hello from exe main
hell from so func
$ _build/libfun.so
hello from so main
I replaced std::cout with printf because I received a segmentation fault - I suspect global constructors are not beeing run and something in iostream destructors makes it go segfault.
I think adding LINK_FLAGS -pie) and target_link_libraries(fun PUBLIC "-pie") makes no point, it's handled with POSITION_INDEPENDENT_CODE property and I guess for shared library it's TRUE anyway (but I am not sure about that).

How to pass arguments to a method loaded from a static library in CPP

I'm trying to write a program to use a static library of a C++ code into another C++ code. The first C++ code is hello.cpp:
#include <iostream>
#include <string.h>
using namespace std;
extern "C" void say_hello(const char* name) {
cout << "Hello " << name << "!\n";
}
int main(){
return 0;
}
The I made a static library from this code, hello.a, using this command:
g++ -o hello.a -static -fPIC hello.cpp -ldl
Here's the second C++ code to use the library, say_hello.cpp:
#include <iostream>
#include <string>
#include <dlfcn.h>
using namespace std;
int main(){
void* handle = dlopen("./hello.a", RTLD_LAZY);
cout<<handle<<"\n";
if (!handle) {
cerr<<"Cannot open library: "<<dlerror()<<'\n';
return 1;
}
typedef void (*hello_t)();
dlerror(); // reset errors
hello_t say_hello = (hello_t) dlsym(handle, "say_hello");
const char *dlsym_error = dlerror();
if (dlsym_error) {
cerr<<"Cannot load symbol 'say_hello': "<<dlsym_error<<'\n';
dlclose(handle);
return 1;
}
say_hello("World");
dlclose(handle);
return 0;
}
Then I compiled say_hello.cpp using:
g++ -W -ldl say_hello.cpp -o say_hello
and ran ./say_hello in the command line. I expected to get Hello World! as output, but I got this instead:
0x8ea4020
Hello ▒▒▒▒!
What is the problem? Is there any trick to make compatibility for method's argument like what we use in ctypes or what?
If it helps I use a lenny.
EDIT 1:
I have changed the code and used a dynamic library, 'hello.so', which I've created using this command:
g++ -o hello.so -shared -fPIC hello.cpp -ldl
The 6th line of the code changed to:
void* handle = dlopen("./hello.so", RTLD_LAZY);
When I tried to compile say_hello.cpp, I got this error:
say_hello.cpp: In function ‘int main()’:
say_hello.cpp:21: error: too many arguments to function
I also tried to compile it using this line:
g++ -Wall -rdynamic say_hello.cpp -ldl -o say_hello
But same error raised. So I removed the argument "World" and the it has been compiled with no error; but when I run the executable, I get the same output like I have mentioned before.
EDIT 2:
Based on #Basile Starynkevitch 's suggestions, I changed my say_hello.cpp code to this:
#include <iostream>
#include <string>
#include <dlfcn.h>
using namespace std;
int main(){
void* handle = dlopen("./hello.so", RTLD_LAZY);
cout<<handle<<"\n";
if (!handle) {
cerr<<"Cannot open library: "<<dlerror()<<'\n';
return 1;
}
typedef void hello_sig(const char *);
void* hello_ad = dlsym(handle, "say_hello");
if (!hello_ad){
cerr<<"dlsym failed:"<<dlerror()<<endl;
return 1;
}
hello_sig* fun = reinterpret_cast<hello_sig*>(hello_ad);
fun("from main");
fun = NULL;
hello_ad = NULL;
dlclose(handle);
return 0;
}
Before that, I used below line to make a .so file:
g++ -Wall -fPIC -g -shared hello.cpp -o hello.so
Then I compiled say_hello.cpp wth this command:
g++ -Wall -rdynamic -g say_hello.cc -ldl -o say_hello
And then ran it using ./say_hello. Now everything is going right. Thanks to #Basile Starynkevitch for being patient about my problem.
Functions never have null addresses, so dlsym on a function name (or actually on any name defined in C++ or C) cannot be NULL without failing:
hello_t say_hello = (hello_t) dlsym(handle, "say_hello");
if (!say_hello) {
cerr<<"Cannot load symbol 'say_hello': "<<dlerror()<<endl;
exit(EXIT_FAILURE);
};
And dlopen(3) is documented to dynamically load only dynamic libraries (not static ones!). This implies shared objects (*.so) in ELF format. Read Drepper's paper How To Use Shared Libraries
I believe you might have found a bug in dlopen (see also its POSIX dlopen specification); it should fail for a static library hello.a; it is always used on position independent shared libraries (like hello.so).
You should dlopen only position independent code shared objects compiled with
g++ -Wall -O -shared -fPIC hello.cpp -o hello.so
or if you have several C++ source files:
g++ -Wall -O -fPIC src1.cc -c -o src1.pic.o
g++ -Wall -O -fPIC src2.cc -c -o src2.pic.o
g++ -shared src1.pic.o src2.pic.o -o yourdynlib.so
you could remove the -O optimization flag or add -g for debugging or replace it with -O2 if you want.
and this works extremely well: my MELT project (a domain specific language to extend GCC) is using this a lot (generating C++ code, forking a compilation like above on the fly, then dlopen-ing the resulting shared object). And my manydl.c example demonstrates that you can dlopen a big lot of (different) shared objects on Linux (typically millions, and hundred of thousands at least). Actually the limitation is the address space.
BTW, you should not dlopen something having a main function, since main is by definition defined in the main program calling (perhaps indirectly) dlopen.
Also, order of arguments to g++ matters a lot; you should compile the main program with
g++ -Wall -rdynamic say_hello.cpp -ldl -o say_hello
The -rdynamic flag is required to let the loaded plugin (hello.so) call functions from inside your say_hello program.
For debugging purposes always pass -Wall -g to g++ above.
BTW, you could in principle dlopen a shared object which don't have PIC (i.e. was not compiled with -fPIC); but it is much better to dlopen some PIC shared object.
Read also the Program Library HowTo and the C++ dlopen mini-howto (because of name mangling).
example
File helloshared.cc (my tiny plugin source code in C++) is
#include <iostream>
#include <string.h>
using namespace std;
extern "C" void say_hello(const char* name) {
cout << __FILE__ << ":" << __LINE__ << " hello "
<< name << "!" << endl;
}
and I am compiling it with:
g++ -Wall -fPIC -g -shared helloshared.cc -o hello.so
The main program is in file mainhello.cc :
#include <iostream>
#include <string>
#include <dlfcn.h>
#include <stdlib.h>
using namespace std;
int main() {
cout << __FILE__ << ":" << __LINE__ << " starting." << endl;
void* handle = dlopen("./hello.so", RTLD_LAZY);
if (!handle) {
cerr << "dlopen failed:" << dlerror() << endl;
exit(EXIT_FAILURE);
};
// signature of loaded function
typedef void hello_sig_t(const char*);
void* hello_ad = dlsym(handle,"say_hello");
if (!hello_ad) {
cerr << "dlsym failed:" << dlerror() << endl;
exit(EXIT_FAILURE);
}
hello_sig_t* fun = reinterpret_cast<hello_sig_t*>(hello_ad);
fun("from main");
fun = NULL; hello_ad = NULL;
dlclose(handle);
cout << __FILE__ << ":" << __LINE__ << " ended." << endl;
return 0;
}
which I compile with
g++ -Wall -rdynamic -g mainhello.cc -ldl -o mainhello
Then I am running ./mainhello with the expected output:
mainhello.cc:7 starting.
helloshared.cc:5 hello from main!
mainhello.cc:24 ended.
Please notice that the signature hello_sig_t in mainhello.cc should be compatible (homomorphic, i.e. the same as) with the function say_hello of the helloshared.cc plugin, otherwise it is undefined behavior (and you probably would have a SIGSEGV crash).

Writing/Using C++ Libraries

I am looking for basic examples/tutorials on:
How to write/compile libraries in C++ (.so files for Linux, .dll files for Windows).
How to import and use those libraries in other code.
The code
r.cc :
#include "t.h"
int main()
{
f();
return 0;
}
t.h :
void f();
t.cc :
#include<iostream>
#include "t.h"
void f()
{
std::cout << "OH HAI. I'M F." << std::endl;
}
But how, how, how?!
~$ g++ -fpic -c t.cc # get t.o
~$ g++ -shared -o t.so t.o # get t.so
~$ export LD_LIBRARY_PATH="." # make sure t.so is found when dynamically linked
~$ g++ r.cc t.so # get an executable
The export step is not needed if you install the shared library somewhere along the global library path.