Writing a DTrace consumer in C - c

I'd like to write a C program on FreeBSD 10.1 that implements a DTrace consumer using libdtrace.
I know that I need to start with a call to dtrace_open() - e.g. I have found this old presentation, but I can't get even started, since there is no dtrace.h installed (only in the system source tree).
The shared library is installed, e.g. the /usr/sbin/dtrace tool included with FreeBSD can act as a DTrace consumer and this tool links to /lib/libdtrace.so.2 (which is also pointed to via a symbolic link from /usr/lib/libdtrace.so).
Any basic example, including build instructions (FreeBSD 10.1 / clang) will help me a lot.
The actual goal of writing a custom consumer is creating a CFFI based wrapper usable in Python and PyPy. Means: above C program is just to get started, learn and then proceed.
CFFI is the recommended, modern, high-performance way of interfacing PyPy with shared libraries.
CFFI can be used at the ABI and API level. The latter requires a header file to include, the former requires to declare the stuff used from a lib.
Adapted from Adam's answer, here is a complete example that works on FreeBSD 10.1.
Makefile:
all:
cc \
-I /usr/src/cddl/compat/opensolaris/include \
-I /usr/src/cddl/contrib/opensolaris/lib/libdtrace/common/ \
-I /usr/src/sys/cddl/compat/opensolaris \
-I /usr/src/sys/cddl/contrib/opensolaris/uts/common/ \
hello_dtrace.c \
-l dtrace -l proc -l ctf -l elf -l z -l rtld_db -l pthread -l util \
-o hello_dtrace
hello_dtrace.c:
#include <dtrace.h>
#include <signal.h>
#include <stdio.h>
static dtrace_hdl_t* g_dtp;
static int chewrec (const dtrace_probedata_t *data, const dtrace_recdesc_t *rec, void *arg) {
printf("chewing dtrace record ..\n");
// A NULL rec indicates that we've processed the last record.
if (rec == NULL) {
return (DTRACE_CONSUME_NEXT);
}
return (DTRACE_CONSUME_THIS);
}
static const char* g_prog = "BEGIN { printf(\"hello from dtrace\\n\"); }";
//static const char* g_prog = "syscall::open*:entry { printf(\"%s %s\\n\", execname, copyinstr(arg0)); }";
static int g_intr;
static int g_exited;
static void intr (int signo) {
g_intr = 1;
}
int main (int argc, char** argv) {
int err;
if ((g_dtp = dtrace_open(DTRACE_VERSION, 0, &err)) == NULL) {
fprintf(stderr, "failed to initialize dtrace: %s\n", dtrace_errmsg(NULL, err));
return -1;
}
printf("Dtrace initialized\n");
(void) dtrace_setopt(g_dtp, "bufsize", "4m");
(void) dtrace_setopt(g_dtp, "aggsize", "4m");
printf("dtrace options set\n");
dtrace_prog_t* prog;
if ((prog = dtrace_program_strcompile(g_dtp, g_prog, DTRACE_PROBESPEC_NAME, 0, 0, NULL)) == NULL) {
fprintf(stderr, "failed to compile dtrace program\n");
return -1;
} else {
printf("dtrace program compiled\n");
}
dtrace_proginfo_t info;
if (dtrace_program_exec(g_dtp, prog, &info) == -1) {
fprintf(stderr, "failed to enable dtrace probes\n");
return -1;
} else {
printf("dtrace probes enabled\n");
}
struct sigaction act;
(void) sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = intr;
(void) sigaction(SIGINT, &act, NULL);
(void) sigaction(SIGTERM, &act, NULL);
if (dtrace_go(g_dtp) != 0) {
fprintf(stderr, "could not start instrumentation\n");
return -1;
} else {
printf("instrumentation started ..\n");
}
int done = 0;
do {
if (!g_intr && !done) {
dtrace_sleep(g_dtp);
}
if (done || g_intr || g_exited) {
done = 1;
if (dtrace_stop(g_dtp) == -1) {
fprintf(stderr, "could not stop tracing\n");
return -1;
}
}
switch (dtrace_work(g_dtp, stdout, NULL, chewrec, NULL)) {
case DTRACE_WORKSTATUS_DONE:
done = 1;
break;
case DTRACE_WORKSTATUS_OKAY:
break;
default:
fprintf(stderr, "processing aborted");
return -1;
}
} while (!done);
printf("closing dtrace\n");
dtrace_close(g_dtp);
return 0;
}
To run:
[oberstet#brummer2 ~/hello_dtrace]$ make; sudo ./hello_dtrace
cc -I /usr/src/cddl/contrib/opensolaris/lib/libdtrace/common/ -I /usr/src/sys/cddl/compat/opensolaris -I /usr/src/sys/cddl/contrib/opensolaris/uts/common/ hello_dtrace.c -l dtrace -l proc -l ctf -l elf -l rtld_db -l z -l pthread -l util -o hello_dtrace
Dtrace initialized
dtrace options set
dtrace program compiled
dtrace probes enabled
instrumentation started ..
chewing dtrace record ..
hello from dtrace
chewing dtrace record ..
^Cclosing dtrace

The libdtrace API isn't necessarily intended for stable consumers, but it's pretty easy to learn from some existing consumers to get started. The simplest and most modern is plockstat, the illumos utility for user-land locking statistics.
Here's the basic flow of a simple DTrace consumer program:
dtrace_open() to get a dtrace_hdl_t, a handle for other libdtrace interactions
dtrace_setopt() to configure options (-x flag to dtrace(1M))
dtrace_strcompile() to compile a string of your D program
dtrace_program_exec() to send that program to the kernel
dtrace_go() to instrument the system and start recording data
dtrace_close() to clean up at the end
For the main data collection loop (between dtrace_go() and dtrace_close()) do the following:
dtrace_sleep() to pause according to the DTrace options (switchrate and aggrate)
dtrace_work() to process traced data
dtrace_stop() to abort
See the main loop in plockstat.c for more.
For other simple DTrace consumers, check out intrstat and lockstat. For the kitchen sink, check out the code the the dtrace(1M) command-line utility.

dtrace is considered a system internal interface, and writing a custom consumer is not something that is supported.
The best you can do is check out a copy of the FreeBSD source tree and build your code from there. This actually isn't terribly difficult. Obviously, dtrace(1) is the canonical dtrace consumer, so you can look at its implementation for examples of how to use libdtrace. Additionally dtrace.h has a huge amount of comments explaining data structures and internals.
Building the files in the context of the source tree is pretty easy; the FreeBSD source tree layout is simple and writing Makefiles to build binaries in a self-contained manner is basically given to you "for free".
Pertinent points:
Clone the dtrace(1) Makefile into a new directory in your source tree checkout. Modify the Makefile such that .PATH is correct, and set PROG and SRCS to include the set of sources comprising your custom consumer.
Clone dtrace.c to your source directory (whereever you pointed .PATH to) and make changes as needed. Although the source is nearly 2,000 lines, much of it is support code. If you're just looking to clone a subset of the functionality, you'll find that most of the options to the binary are implemented in self-contained functions, and it should therefore be fairly easy to trim dtrace.c down to a bare-minimum form.
Without knowing what specifically you're needing to do with your custom consumer, it's difficult to tell you what else you'll need to call into. I'm assuming you'll probably want compile_file in there as well as exec_prog. Of course, your needs may differ.
The dtrace.h you will be using has a number of comments about the various interfaces provided.
Hopefully this is enough to get you started; unfortunately, there's not a way to do what you want from a vanilla install. You could, of course, hack up the relevant Makefiles to install the necessary header files and create your own internal distribution. But that seems like more pain than it's worth.

Related

High CPU Utilization(~100%) while runing Tcl_DoOneEvent using LSF/bsub infrastructure

I have created my own event loop in Tcl as below. When i run the below code using tclsh interactively, CPU Utilization is close to 0% and when i run the same run using bsub, CPU Utilization shoots up to 100%.
I have even tried making read call blocking using below and that doesn't help too.
int flag = fcntl(0, F_GETFL);
flag = flag & (~O_NONBLOCK);
(void) fcntl(0, F_SETFL, (long)flag);
What is the reason here and how do i solve this problem?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <tcl.h>
#include <errno.h>
#include <fcntl.h>
void fwStdinKeyHandler(ClientData clientData, int mask)
{
unsigned char c = 0;
int rc = read(STDIN_FILENO, &c, 1);
//printf("rc is : %d\n",rc);
while (rc < 1 && errno == EINTR) {}
}
static void MainLoop(void)
{
Tcl_CreateFileHandler(STDIN_FILENO, TCL_READABLE, fwStdinKeyHandler, NULL);
while (1) {
Tcl_DoOneEvent(0);
}
fprintf(stdout,"Exit MainLoop\n");
fflush(stdout);
}
static int Hello_Cmd(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
Tcl_SetMainLoop(MainLoop);
return TCL_OK;
}
/*
* Hello_Init -- Called when Tcl loads your extension.
*/
int DLLEXPORT Cmd_Init(Tcl_Interp *interp)
{
if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
return TCL_ERROR;
}
/* changed this to check for an error - GPS */
if (Tcl_PkgProvide(interp, "Hello", "1.0") == TCL_ERROR) {
return TCL_ERROR;
}
Tcl_CreateObjCommand(interp, "doone_loop", Hello_Cmd, NULL, NULL);
return TCL_OK;
}
How to make?
1. File saved in,say, hello.c
2. gcc -fpic -c hello.c -I/usr/local/include
3. gcc -shared hello.o -o libcmd.so
How to run?
runme file contains:
load libcmd.so
doone_loop
/usr/bin/tclsh runme => CPU Utilization close to 0%
bsub -q interactive -m "/usr/bin/tclsh runme" => CPU Utilization close to 100%
I think the problem is that when the command is run inside (the system that) bsub (talks to), it is run non-interactively. In particular, stdin is probably coming from either a file or /dev/null, both of which are always readable to the notifier (which is a tamed version of a bunch of low level system calls). That means your code is called back into a lot, almost as if it is busy-looping, generating a lot of CPU usage.
Since this is an operating system level behaviour, your approach simply won't work. You'll need to detect if you are in this situation (perhaps with isatty()?) and not install the event handler if it won't work right.
For non-interactive shell, there is no terminal and hence, no stdin channel, so read call returns zero. So, we need to add below code in fwStdinkeyhandler after read returns 0.
if(rc==0) {
Tcl_DeleteFileHandler(0);
}

How to append "ifconfig" in a file .txt in C language?

I'm trying to get my own ip addres with C.
The idea is to get the output of "ifconfig", put it in a .txt file and extract the inet and the inet6 values.
I stack trying to write the ifconfig output in the .txt file:
#include <stdio.h>
#include <stdlib.h>
#include <string>
int main ()
{
char command[1000];
strcpy(command, "ifconfig");
system(command);
FILE *fp = fopen("textFile.txt", "ab+");
//... how to write the output of 'system(command)' in the textFile.txt?
fclose(fp);
//... how to scraping a file .text in C ???
return 0;
}
Thank you for any help and suggestion,
Stefano
You actually want to use system calls to accomplish this - rather than running the ifconfig command.
There's a similar question here for Linux: using C code to get same info as ifconfig
(Since ifconfig is a Linux command, I'm assuming you're asking about Linux).
The general gist was to use the ioctl() system call.
Otherwise, you'll be forking your process to split it into two, creating a pipe from the output of the child to the input of the parent, and calling exec on the child in order to replace it with "ifconfig". Then, you'll have to parse the string if you want to get anything useful out of it.
In Linux, use man 7 netdevice describes the interface you can use, as Anish Goyal already answered.
This is very simple and robust, and uses very little resources (since it is just a few syscalls). Unfortunately, it is Linux specific, and makes the code nonportable.
It is possible to do this portably. I describe the portable option here, because the Linux-specific one is rather trivial. Although the approach is quite complicated for just obtaining the local host IP addresses, the pattern is surprisingly often useful, because it can hide system-specific quirks, while easily allowing system administrators to customize the behaviour.
The idea for a portable solution is that you use a small helper program or shell script to obtain the information, and have it output the information in some easy-to-parse format to your main program. If your application is named yourapp, it is common to install such helpers in /usr/lib/yourapp/, say /usr/lib/yourapp/local-ip-addresses.
Personally, I'd recommend using a shell script (so that system admins can trivially edit the helpers if they need to customize the behaviour), and an output format where each interface is on its own line, fields separated by spaces, perhaps
inet interface-name ipv4-address [ hostname ]*
inet6 interface-name ipv6-address [ hostname ]*
i.e. first token specifies the address family, second token the interface name, third the address, optionally followed by the hostnames or aliases corresponding to that address.
As to the helper program/shell script itself, there are two basic approaches:
One-shot
For example, parsing LANG=C LC_ALL=C ip address output in Linux.
The program/script will exit after the addresses have been printed.
Continuous
The program/script will print the ip address information, but instead of exiting, it will run as long as the pipe stays open, providing updates if interfaces are taken down or come up.
In Linux, a program/script could use DBUS or NetworkManager to wait for such events; it would not need to poll (that is, repeatedly check the command output).
A shell script has the extra benefit that it can support multiple distributions, even operating systems (across POSIX systems at least), at the same time. Such scripts often have a similar outline:
#!/bin/sh
export LANG=C LC_ALL=C
case "$(uname -s)" in
Linux)
if [ -x /bin/ip ]; then
/bin/ip -o address | awk \
'/^[0-9]*:/ {
addr = $4
sub(/\/.*$/, "", addr)
printf "%s %s %s\n", $3, $2, addr
}'
exit 0
elif [ -x /sbin/ifconfig ]; then
/sbin/ifconfig | awk \
'BEGIN {
RS = "[\t\v\f\r ]*\n"
FS = "[\t\v\f ]+"
}
/^[0-9A-Za-z]/ {
iface = $1
}
/^[\t\v\f ]/ {
if (length(iface) > 0)
for (i = 1; i < NF-1; i++)
if ($i == "inet") {
addr = $(i+1)
sub(/^addr:/, "", addr)
printf "inet %s %s\n", iface, addr
} else
if ($i == "inet6") {
addr = $(i+2)
sub(/\/.*$/, "", addr)
printf "inet6 %s %s\n", iface, addr
}
}'
exit 0
fi
;;
# Other systems?
esac
printf 'Cannot determine local IP addresses!\n'
exit 1
The script sets the locale to C/POSIX, so that the output of external commands will be in the default locale (in English and so on). uname -s provides the kernel name (Linux for Linux), and further checks can be done using e.g. the [ shell command.
I only implemented the scriptlet for Linux, because that's the machine I'm on right now. (Both ip and ifconfig alternatives work on my machine, and provide the same output -- although you cannot expect to get the interfaces in any specific order.)
The situations where a sysadmin might need to edit this particular helper script includes as-yet-unsupported systems, systems with new core tools, and systems that have interfaces that should be excluded from normal interface lists (say, those connected to a internal sub-network that are reserved for privileged purposes like DNS, file servers, backups, LDAP, and so on).
In the C application, you simply execute the external program or shell script using popen("/usr/lib/yourapp/local-ip-addresses", "r"), which provides you a FILE * that you can read as if it was a file (except you cannot seek or rewind it). After you have read everything from the pipe, pclose() the handle:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
/* ... */
FILE *in;
char *line_ptr = NULL;
size_t line_size = 0;
ssize_t line_len;
int status;
in = popen("/usr/lib/myapp/local-ip-addresses", "r");
if (!in) {
fprintf(stderr, "Cannot determine local IP addresses: %s.\n", strerror(errno));
exit(EXIT_FAILURE);
}
while (1) {
line_len = getline(&line_ptr, &line_size, in);
if (line_len < 1)
break;
/* Parse line_ptr */
}
free(line_ptr);
line_ptr = NULL;
line_size = 0;
if (ferror(in) || !feof(in)) {
pclose(in);
fprintf(stderr, "Read error while obtaining local IP addresses.\n");
exit(EXIT_FAILURE);
}
status = pclose(in);
if (!WIFEXITED(status) || WEXITSTATUS(status)) {
fprintf(stderr, "Helper utility /usr/lib/myapp/local-ip-addresses failed to obtain local IP addresses.\n");
exit(EXIT_FAILURE);
}
I omitted the parsing code, because there are so many alternatives -- strtok(), sscanf(), or even a custom function that splits the line into tokens (and populates an array of pointers) -- and my own preferred option is the least popular one (last one, a custom function).
While the code needed for just this task is almost not worth the while, applications that use this approach tend to use several of such helpers. Then, of course, it makes sense to choose the piped data format in a way that allows easy parsing, but supports all use cases. The amortized cost is then much easier to accept, too.

mlock a program from a wrapper

Just a quick question (I hope). How would you allocate an address space via mlock and then launch an application within that space?
For instance I have a binary that launches from a wrapper program that configures the environment. I only have access to the wrapper code and would like to have the binary launch in a certain address space. Is it possible to do this from the wrapper?
Thanks!
If you have the sources for the program, add a command-line option so that the program calls mlockall(MCL_CURRENT | MCL_FUTURE) at some point. That locks it in memory.
If you want to control the address spaces the kernel loads the program into, you need to delve into kernel internals. Most likely, there is no reason to do so; only people with really funky hardware would.
If you don't have the sources, or don't want to recompile the program, then you can create a dynamic library that executes the command, and inject it into the process via LD_PRELOAD.
Save the following as lockall.c:
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
static void wrerr(const char *p)
{
if (p) {
const char *q = p + strlen(p);
ssize_t n;
while (p < q) {
n = write(STDERR_FILENO, p, (size_t)(q - p));
if (n > 0)
p += n;
else
if (n != -1 || errno != EINTR)
return;
}
}
}
static void init(void) __attribute__((constructor));
static void init(void)
{
int saved_errno = errno;
if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
const char *errmsg = strerror(errno);
wrerr("Cannot lock all memory: ");
wrerr(errmsg);
wrerr(".\n");
exit(127);
} else
wrerr("All memory locked.\n");
errno = saved_errno;
}
Compile it to a dynamic library liblockall.so using
gcc -Wall -O2 -fPIC -shared lockall.c -Wl,-soname,liblockall.so -o liblockall.so
Install the library somewhere typical, for example
sudo install -o 0 -g 0 -m 0664 liblockall.so /usr/lib/
so you can run any binary, and lock it into memory, using
LD_PRELOAD=liblockall.so binary arguments..
If you install the library somewhere else (not listed in /etc/ld.so.conf), you'll need to specify path to the library, like
LD_PRELOAD=/usr/lib/liblockall.so binary arguments..
Typically, you'll see the message Cannot lock all memory: Cannot allocate memory. printed by the interposed library, when running commands as a normal user. (The superuser, or root, typically has no such limit.) This is because for obvious reasons, most Linux distributions limit the amount of memory an unprivileged user can lock into memory; this is the RLIMIT_MEMLOCK resource limit. Run ulimit -l to see the per-process resource limits currently set (for the current user, obviously).
I suggest you set a suitable limit of how much memory the process can run, running e.g. ulimit -l 16384 bash-built-in before executing the (to set the limit to 16384*1024 bytes, or 16 MiB), if running as superuser (root). If the process leaks memory, instead of crashing your machine (because it locked all available memory), the process will die (from SIGSEGV) if it exceeds the limit. That is, you'd start your process using
ulimit -l 16384
LD_PRELOAD=/usr/lib/liblockall.so binary arguments..
if using Bash or dash shell.
If running as a dedicated user, most distributions use the pam_limits.so PAM module to set the resource limits "automatically". The limits are listed either in the /etc/security/limits.conf file, or in a file in the /etc/security/limits.d/ subdirectory, using this format; the memlock item specifies the amount of memory each process can lock, in units of 1024 bytes. So, if your service runs as user mydev, and you wish to allow the user to lock up to 16 megabytes = 16384*1024 bytes per process, then add line mydev - memlock 16384 into /etc/security/limits.conf or /etc/security/limits.d/mydev.conf, whichever your Linux distribution prefers/suggests.
Prior to PAM, shadow-utils were used to control the resource limits. The memlock resource limit is specified in units of 1024 bytes; a limit of 16 megabytes would be set using M16384. So, if using shadow-utils instead of PAM, adding line mydev M16384 (followed by whatever the other limits you wish to specify) to /etc/limits should do the trick.

linux application remote debugging without gdb

I wrote a linux program as below:
int g_para1 = 10;
int g_para2 = 11;
void SetPara(int para1, int para2)
{
g_para1 = para1;
g_para2 = para2;
}
void DumpPara()
{
printf("g_para1 = %d, g_para2 = %d\n", g_para1,g_para2);
}
void init()
{
int pid;
if(pid = fork())
exit(0);
else if(pid < 0)
exit(1);
setsid();
if(pid = fork())
exit(0);
else if(pid < 0)
exit(1);
return;
}
int main()
{
signal(SIGCHLD, SIG_IGN);
init();
while(1)
{
DumpPara();
sleep(10);
}
return 0;
}
And then compile and run it in the shell
gcc -o test test.c
./test
it will show the print "g_para1 = 10, g_para2 = 11" every 10 seconds.
My question is:
If I execute "SetPara 20, 30" in the shell, i want the print shows "g_para1 = 20, g_para2 = 30".
What should i do to make it work?
If I do nothing, it will show
SetPara 20,30
SetPara: command not found
First, an application (in C, compiled with debug info, e.g. with gcc -Wall -g) can be remotely debugged. If you have ssh available, you simply debug using gdb thru a terminal connection via ssh. Otherwise, read the documentation of GDB, it has a chapter on remote debugging.
Then, it looks like you wish to embed your application into a scripting language interpreter. Then try lua, or GNU guile, or perhaps even python or ocaml. Notice that embedding your application inside an interpreter (or symetrically, putting an interpreter inside your application) is a heavy architectural decision and has major impacts on its design.
BTW, some Unix shells can be extended thru plugins, e.g. zsh (with modules...). Plugins use dynamic loading facilities like dlopen(3) (you might consider using them in your application).
But an ordinary shell can only start external programs using fork(2) & execve(2)
BTW, you might consider making your application behave as a Web server by using some HTTP server library inside it, e.g. libonion. You could also consider remote procedure call techniques, e.g. JSON RPC
Read also Advanced Linux Programming
As for your improved question, just define a convention in main arguments: you could want that if you invoke your program with arguments --setpara 23 45 it will call SetPara(23,45) e.g. with code like:
int main (int argc, char**argv) {
if (argc>3 && !strcmp[argv[1], "--setpara")) {
int x = atoi(argv[2]);
int y = atoi(argv[3]);
SetPara (x,y);
}
but you should do serious arguments passing e.g. with getopt_long (and accept --help & --version). See this answer and that one.
If you want SetPara to be called elsewhere in your code, the arguments to main should trigger other behavior (e.g. initializing global data, initializing an embedded interpreter, etc.).
Notice that each process has its own address space in virtual memory. See also this answer.
PS: your question is very unclear; I tried to give various ways of approaching your issues, which I am mostly guessing. You should study -for inspiration at least- the source code of some existing free software related to your goals.

How can dlerror() return NULL if dlopen() fails?

I am working on system that loads all *.so modules library automatically by call a script.
I tried to update one of the modules to support XML-RPC. I used the library ibxmlrpc-c3-dev on Ubuntu 10.10. The problem that dlopen() fails after my changes and dlerror() returns NULL. The compilation does not return any error.
How can I debug and fix this issue? Below is the code:
#include "stdlib.h"
#include "stdio.h"
#ifndef WIN32
#include "unistd.h"
#endif
#include "xmlrpc-c/base.h"
#include "xmlrpc-c/server.h"
#include "xmlrpc-c/server_abyss.h"
#include "config.h" /* information about this build environment */
And, I added this function , most of the lines are commented out ,even though dlopen() fails:
int RPC_Server(int const port) {
// xmlrpc_server_abyss_parms serverparm;
//xmlrpc_registry * registryP;
xmlrpc_env env;
xmlrpc_env_init(&env);
//registryP = xmlrpc_registry_new(&env);
// xmlrpc_registry_add_method(
// &env, registryP, NULL, "sample.add", &sample_add, NULL);
/* In the modern form of the Abyss API, we supply parameters in memory
like a normal API. We select the modern form by setting
config_file_name to NULL:
*/
// serverparm.config_file_name = NULint
RPC_Server(int const port) {
// xmlrpc_server_abyss_parms serverparm;
//xmlrpc_registry * registryP;
xmlrpc_env env;
xmlrpc_env_init(&env);
//registryP = xmlrpc_registry_new(&env);
// xmlrpc_registry_add_method(
// &env, registryP, NULL, "sample.add", &sample_add, NULL);
/* In the modern form of the Abyss API, we supply parameters in memory
like a normal API. We select the modern form by setting
config_file_name to NULL:
*/
// serverparm.config_file_name = NULL;
// serverparm.registryP = registryP;
// serverparm.port_number = port;
// serverparm.log_file_name = "/tmp/xmlrpc_log";
// printf("Running XML-RPC server...\n");
// xmlrpc_server_abyss(&env, &serverparm, XMLRPC_APSIZE(log_file_name));
/* xmlrpc_server_abyss() never returns */
return 0;
}L;
// serverparm.registryP = registryP;
// serverparm.port_number = port;
// serverparm.log_file_name = "/tmp/xmlrpc_log";
// printf("Running XML-RPC server...\n");
// xmlrpc_server_abyss(&env, &serverparm, XMLRPC_APSIZE(log_file_name));
/* xmlrpc_server_abyss() never returns */
return 0;
}
and this is the code the is used to load modules
#ifndef RTLD_NOW
#define RTLD_NOW DL_LAZY
#endif
void* handle;
char* error;
handle=dlopen(mod->binary_file, RTLD_NOW);
if (!handle){
LOG( " could not open file [%s]: %s\n",
mod_cfg->binary_file, dlerror() );
return 0;
}
In this code:
handle=dlopen(mod->binary_file, RTLD_NOW);
if (!handle) {
LOG( " could not open file [%s]: %s\n",
mod_cfg->binary_file, dlerror() );
the most likely way I can think of for the dlerror() to return NULL here is if LOG itself calls one of the dl* routines (which would clear the error state that dlerror returns).
So,
show us what LOG macro (if indeed it is a macro) expands to, and
run the program under GDB, set breakpoints on dlopen, dlmopen, dlsym and dlvsym, and observe that one of them is called after your call to dlopen above and before your call to dlerror.
I would use a debugger like gdb.
If you cannot use it, try to use strace or ltrace on the process doing the dlopen
Also, clear errno before calling dlopen and display it (or print it under the debugger) just after the failing dlopen.
Check with file, objdump, and nm -D that your dlopen-ed *.so file has all the required properties (e.g. symbols).
Perhaps the memory address space of the process doing the dlopen is so full (or has reached some resource limits) that some internal malloc inside libdl.so fails (e.g. the one used by dlerror).
handle=dlopen(mod->binary_file, RTLD_NOW);
if (!handle){
string errmsg = string(dlerror());
LOG( " could not open file [%s]: %s\n",
mod_cfg->binary_file, errmsg.c_str() );
return 0;
}
I come across the same problem when using BoostLog, and above is my solution.
I suppose LOG affects dlerror().
I had this problem with VirtualBox and Qt5 on an Ubuntu system; Qt5 couldn't dynamically load libqxcb.so, and after some debugging it turned out that both dlopen() and dlerror() were returning NULL.
The cause turned out to be because VirtualBox is a setuid binary and can't dlopen libraries in directories that aren't owned by root. In my case, the solution was as simple as running:
sudo chown root:root /usr/lib/x86_64-linux-gnu
My non-root user (with UID 1000) owned /usr/lib/x86_64-linux-gnu somehow, likely some error on my part. But after returning ownership to root, VirtualBox—and by extension Qt—could load libraries just fine.
I suspect this may have something to do with AppArmor, but I've no experience with it so I can't say for sure.
Above all, you should definitely use dlerror.

Resources