ovedpo15 has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks!
This question is more sort of a code-review. I'll explain what I'm trying to do.
I'm using Linux env. I'm looking for a way to get all shared libraries of path. So basically something like:
%libs = get_libs($path);
Now I'm trying to figure out how I need to create my own `get_libs` sub or is there already a made basic module I should use (which probably will do a better job than me, but it should be pretty basic since it is really hard to make IT to install the module on all the machines). Looked at CPAN first and found out Devel::SharedLibs which looks like something similiar but it is not installed in IT.
The way I thought of implementing the `get_libs` sub is by using:
ldd <path>
And then parsing the output of it. Although I'm only working on Linux SUSE, the problem is it's bad practice to parse some command's output. For example at some point, the format could change. If there was an option like `--json` or some other formatting option, to get the output as JSON and then you know how to parse it, then it would be better. But the output of `ldd` is:
linux-vdso.so.1 (0x00007ffff733a000) + libems_sh.so => not found + libdl.so.2 => /lib64/libdl.so.2 (0x00007ffff7b337000) + libtq.so => not found
I could parse this output like so:
my $cmd = "ldd $path"; my ($stdout, $stderr, $exit_status) = capture { system($cmd); }; next if (exit_status); foreach my $line (split(/\n/, $stdout)) { if ($line =~ /(.*)\s*=>\s*(.*)/) { my $lib_path = $2; if ($lib_path =~ /(.*)\s+(.*)/) { $lib_path = $1; $paths{$lib_path}++; print("Found $lib_path of $path\n"); } else { print("Invalid line: $line of $path\n"); } } else { print("Invalid line: $line of $path\n"); } }
I really don't like my code. It feels too specific (I look for strings of -something- => -someting- -something- basically). Is it possible to review my code and suggest improvements or other ideas on how to solve this challenge?

Replies are listed 'Best First'.
Re: How to get all shared libs of a path?
by afoken (Chancellor) on Jun 20, 2021 at 17:29 UTC

    A note on running ldd, quoting the man page:

    Security

    In the usual case, ldd invokes the standard dynamic linker (see ld.so(8)) with the LD_TRACE_LOADED_OBJECTS environment variable set to 1, which causes the linker to display the library dependencies. Be aware, however, that in some circumstances, some versions of ldd may attempt to obtain the dependency information by directly executing the program. Thus, you should never employ ldd on an untrusted executable, since this may result in the execution of arbitrary code. A safer alternative when dealing with untrusted executables is:

    $ objdump -p /path/to/program | grep NEEDED

    A note on dependencies: Make the distribution-provided package manager (apt/dpkg, rpm, yum, whatever) resolve dependencies. Put the executables into distribution-specific packages (see distribution documentation for details), hand out the packages instead of bare executables, have the IT department install the packages instead of the bare executables. https://serverfault.com/questions/18620/how-to-create-an-rpm-for-suse may help creating SuSE packages.

    (Yes, I know my favorite distribution Slackware is an exception to the rule: You are expected to resolve dependencies by hand, or simply install ALL packages. But most other distributions do an excellent job at managing dependencies.)

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
      Hi, thank you for your reply. This actually helps a lot. I want to find the shared libs in order to find the rpm packages related to the path. I'm using zypper and rpm. I use the following command to find the related packages:
      rpm -qf --queryformat "[%{NAME}]" path
      In the path section I also insert the shared libs to find their related packages. It works with the code I provided but is there a better way of doing it without parsing the output of ldd and puting each lib path into the command above?
        I want to find the shared libs in order to find the rpm packages related to the path.

        That information should also be found in the Makefile (or the tool that generates it), at least the basic library name.

        Also, as documented in the ldd man page, objdump -p can be used to extract the names of shared libraries from the binary, without risking to accidentally executing it. What you get is a list of library names. Search CPAN, I would be surprised if no one has written a module to extract that information from an ELF file without running objdump.

        Now that you got a list of library names, read the ld.so man page to find out how to create a list of possible file names from each library. ld.so reads /etc/ld.so.cache, which is binary noise containing a list of directories to search and an ordered list of candidate libraries. It is generated by ldconfig, which also has a man page. ldconfig reads /etc/ld.so.conf, the format of that file is documented in the ldconfig man page. Assuming that ld.so.conf was not changed after the last run of ldconfig, you don't need to read ld.so.cache at all, and can simply read ld.so.conf. That, combined with the rules and the substitued tokens explained in the ld.so man page, is all you need to re-implement in perl. Essentially, just a little bit of string operations and some calls to stat (in form of -f -x $candidate).

        And now, the really bad news:

        That's not sufficient. Nor is ldd.

        Programms can load libraries at runtime. And they do, often without telling you. You don't really want to know what happens when Name Service Switch (see man 5 nss) or PAM (see man 8 pam) start doing their jobs. But the worst part: You can not extract that information from the binary, because it is not (always) contained in the binary. NSS and PAM highly depend on the system configuration. And NSS and PAM are just two examples. Perl loads libraries at runtime every time you use an XS module.

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: How to get all shared libs of a path?
by shmem (Chancellor) on Jun 20, 2021 at 20:48 UTC
    I'm using Linux env. I'm looking for a way to get all shared libraries of path.

    First question: why? for what purpose?
    Second question: in what context? do you mean system context, or your own?

    In my insane $HOME I not only have a ~/bin directory with strange things, setting LD_LIBRARY_PATH and LD_PRELOAD but also other funny places where libs are stuffed away, e.g. perlbrew things and custom XS perl packages which sport their own *.so files.

    I would go the other way round. The linker already knows where to look for shared libraries. That's in /ec/ld.so.conf and /etc/ld.so.conf.d/*. I'd read these files, get the shared libraries living in these directories (excluding symlinks of course). These are the libs you probably want, for the system. For my own lumber-room I'd include my LD_LIBRARY_PATH values in the directory list.

    To get any shared libraries I'd use locate, e.g. locate .so | perl -lnE 'next if -l or !/\.so$/;say' which gets them all.

    Depending on what you want to do, you could ask the package manager about the owning package of each lib, using dpkg -S $lib or rpm -qf $lib. Having those packages, you can do a "reverse depends" for these packages and query the resulting packages for binaries in $PATH.

    I'm just guessing that you want to do some sort of an inventory, in the posted code you are just getting usage count for each lib. There are other reasons to look up libs, e.g. identify suspicious libs which don't pertain to any package. Hence the first question above.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
      Hi, thanks for your suggestions. I think that my mistake is that I didn't explain the big picture. I have a file of paths that define my tool (all the needed paths to run my tool). I want to create a Dockerfile/Podman-file/Sing-file based on those dependencies so the container will be able to run the tool inside it. I want to find all the RPM packages of those paths. For that I use:
      rpm -qf --queryformat "[%{NAME}]" [path]
      But this command does not give me all the packages I need. I noticed that if I use ldd [path] to get all shared libs and then for each lib, run the above RPM command, then I'll get all the needed packages.

      Now that I have explained the big picture, I'll talk about your suggestions. The locate is a good suggestion. I'll have to combine it with the objdump that was mentioned here. The ldd was working pretty well, but is objdump -p /path/to/program | grep NEEDED and then locate (and then rpm find) is a better approach?
      Now that I have explained the big picture (sorry again that it was not in the main post), is it possible to suggest a better approach? Ideally I would want the rpm command to be able to get a path and return the packages of both the path and the shared libs of the path. This way I won't have to use the ldd command.