Skip to content

Commit

Permalink
Improve code structure and fuctionality
Browse files Browse the repository at this point in the history
  • Loading branch information
haampie committed Apr 8, 2020
1 parent f1a918e commit b119da5
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 122 deletions.
81 changes: 46 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,45 @@
# bundler

A tool to bundle your binaries, useful for building small Docker containers or AppImages.
A tool that:
- :deciduous_tree: turns `ldd` into a fancy tree
- :point_up: explains why `ldd` finds shared libraries and why not
- :package: optionally deploys relevant executables and dependencies into a single directory

Current functionality:
- [x] Add executables with `-e` and libraries via `-l`
- [x] Walks the dependency tree like `ld.so` (handles `RPATH`, `RUNPATH` and `LD_LIBRARY_PATH` correctly).
- [x] Uses `/etc/ld.so.conf` or any custom conf file via `-ldconf /path/to/ld.so.conf`
- [x] Skips blacklisted dependencies (and their dependencies) such as libc.so and libstdc++.so.
- [x] Deploy binaries and rewrite their `RUNPATH`s\*
- [ ] i386 (currently it's hardcoded to only deploy x86_64)
- [ ] Ship `chrpath`
## Example 1: listing the dependencies of an executable

![example](doc/screenshot.png)

## Example 2: deploying binaries + dependencies into a folder:
```bash
$ bundler -e $(which bundler) -d bundler.bundle
Dependency tree
bundler
├── libcppglob.so.1 [direct]
│ ├── libstdc++.so.6 (skipped) [ld.so.conf]
│ ├── libgcc_s.so.1 (skipped) [ld.so.conf]
│ └── libc.so.6 (skipped) [ld.so.conf]
├── libstdc++.so.6 (skipped) [ld.so.conf]
├── libgcc_s.so.1 (skipped) [ld.so.conf]
└── libc.so.6 (skipped) [ld.so.conf]

Deploying to "bundler.bundle/usr"
"/home/.../Documents/projects/bundler/build/bundler" => "bundler.bundle/usr/bin/bundler"
"/home/.../Documents/projects/bundler/build/lib/libcppglob.so.1.1.0" => "bundler.bundle/usr/lib/libcppglob.so.1.1.0"
creating symlink "bundler.bundle/usr/lib/libcppglob.so.1"

$ tree bundler.bundle/
bundler.bundle/
└── usr
├── bin
│   └── bundler
└── lib
├── libcppglob.so.1 -> libcppglob.so.1.1.0
└── libcppglob.so.1.1.0

\* Note: `patchelf` seems to be very broken software, so instead I'm using `chrpath`, but this will only patch rpaths _when they already exist in the binary_. Therefore you might still need to add the `yourapp/lib` folder to ld's search paths by running `ldconfig yourapp/lib` or setting `LD_LIBRARY_PATH=yourapp/lib`.
3 directories, 3 files
```

## Build from source
```bash
git clone --recursive https://github.com/haampie/bundler.git
cd bundler
Expand All @@ -22,29 +49,13 @@ cmake -DCMAKE_BUILD_TYPE=Release ..
make
```

```bash
$ ./bundler -e bundler -d Bundler.app
Dependency tree
bundler
|--libcppglob.so.1
| |--libstdc++.so.6 (excluded)
| |--libgcc_s.so.1 (excluded)
| |--libc.so.6 (excluded)
|--libstdc++.so.6 (excluded)
|--libgcc_s.so.1 (excluded)
|--libc.so.6 (excluded)

Deploying to "Bundler.app/usr"
"/home/harmen/Documents/projects/binary_bundler/build/bundler" => "Bundler.app/usr/bin/bundler"
"/home/harmen/Documents/projects/binary_bundler/build/lib/libcppglob.so.1.1.0" => "Bundler.app/usr/lib/libcppglob.so.1"

$ tree Bundler
Bundler.app/
└── usr
├── bin
│   └── bundler
└── lib
└── libcppglob.so.1
## Current functionality
- [x] Add executables with `-e` and libraries via `-l`
- [x] Walks the dependency tree like `ld.so` (handles `RPATH`, `RUNPATH` and `LD_LIBRARY_PATH` correctly).
- [x] Uses `/etc/ld.so.conf` or any custom conf file via `-ldconf /path/to/ld.so.conf`
- [x] Skips blacklisted dependencies (and their dependencies) such as libc.so and libstdc++.so.
- [x] Deploy binaries and rewrite their `RUNPATH`s\*
- [x] Ship `chrpath` and `strip`
- [ ] i386 (currently it's hardcoded to only deploy x86_64)

3 directories, 2 files
```
\* Note: `patchelf` seems to be very broken software, so instead I'm using `chrpath`. The downside is `chrpath` will only patch rpaths _when they already exist in the binary_. Therefore you might still need to add the `yourapp/lib` folder to ld's search paths by running `echo /path/to/yourapp.bundle/lib > /etc/ld.so.conf/my_app.conf && ldconfig` or by setting `LD_LIBRARY_PATH=/path/to/yourapp.bundle/lib`.
Binary file added doc/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion include/bundler/deploy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ namespace fs = std::filesystem;
std::string repeat(std::string_view input, size_t num);

// Copy binaries over, change their rpath if they have it, and strip them
void deploy(std::vector<Elf> const &deps, fs::path const &bin, fs::path const &lib, fs::path const &strip, fs::path const &chrpath);
void deploy(std::vector<Elf> const &deps, fs::path const &bin, fs::path const &lib, fs::path const &chrpath_path, fs::path const &strip_path, bool chrpath, bool strip);
16 changes: 11 additions & 5 deletions include/bundler/deps.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,27 @@
class deps {

public:
deps(std::vector<Elf> &&input, std::vector<fs::path> &&search_paths, std::vector<fs::path> && ld_library_paths);
deps(std::vector<Elf> &&input, std::vector<fs::path> &&ld_so_conf, std::vector<fs::path> && ld_library_paths, bool verbose);

std::vector<Elf> const &get_deps() const;

private:
void explore(Elf const &elf, std::vector<fs::path> &rpaths, size_t depth = 0);
void explore(Elf const &elf, std::vector<fs::path> &rpaths, std::vector<bool> &done);
void explore(Elf const &elf, std::vector<fs::path> &rpaths);

std::optional<Elf> try_paths(std::vector<fs::path> const &paths, fs::path const &so);
std::optional<Elf> search(fs::path const &search, fs::path const &so);
std::optional<Elf> locate(Elf const &parent, fs::path const &so, std::vector<fs::path> const &rpaths, std::vector<fs::path> const &runpaths);
std::optional<Elf> locate_directly(Elf const &parent, fs::path const &so);
std::optional<Elf> locate_by_search(fs::path const &so, std::vector<fs::path> const &rpaths, std::vector<fs::path> const &runpaths);
std::optional<Elf> find_by_paths(fs::path const &so, std::vector<fs::path> const &paths, found_t tag);

size_t m_depth = 0;
std::vector<Elf> m_top_level;
std::vector<fs::path> m_search_paths;
std::vector<fs::path> m_ld_library_paths;
std::vector<fs::path> m_ld_so_conf;
std::vector<fs::path> m_default_paths{"/lib", "/usr/lib"};
std::unordered_set<fs::path, PathHash> m_visited;

std::vector<Elf> m_all_binaries;

bool m_verbose;
};
5 changes: 4 additions & 1 deletion include/bundler/elf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ namespace fs = std::filesystem;

enum class deploy_t { EXECUTABLE, LIBRARY };

enum class found_t { NONE, DIRECT, RPATH, LD_LIBRARY_PATH, RUNPATH, LD_SO_CONF, DEFAULT_PATHS };

struct Elf {
deploy_t type;
found_t found_via;
std::string name;
fs::path abs_path;
std::vector<fs::path> runpaths;
Expand All @@ -34,4 +37,4 @@ fs::path apply_substitutions(fs::path const &rpath, fs::path const &cwd);
std::vector<fs::path> split_paths(std::string_view raw_path);

// Try to create an elf from a path
std::optional<Elf> from_path(deploy_t type, fs::path path_str);
std::optional<Elf> from_path(deploy_t type, found_t found_via, fs::path path_str);
18 changes: 11 additions & 7 deletions src/deploy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include <termcolor/termcolor.hpp>

// Copy binaries over, change their rpath if they have it, and strip them
void deploy(std::vector<Elf> const &deps, fs::path const &bin, fs::path const &lib, fs::path const &strip, fs::path const &chrpath) {
void deploy(std::vector<Elf> const &deps, fs::path const &bin, fs::path const &lib, fs::path const &chrpath_path, fs::path const &strip_path, bool chrpath, bool strip) {
for (auto const &elf : deps) {

// Go through all symlinks.
Expand All @@ -28,12 +28,16 @@ void deploy(std::vector<Elf> const &deps, fs::path const &bin, fs::path const &l
auto rpath = (elf.type == deploy_t::EXECUTABLE ? "\\$ORIGIN/../lib" : "\\$ORIGIN");

// Silently patch the rpath and strip things; let's not care if it fails.
std::stringstream chrpath_cmd;
chrpath_cmd << chrpath << " -c -r \"" << rpath << "\" " << deploy_path;
exec(chrpath_cmd.str().c_str());
if (chrpath) {
std::stringstream chrpath_cmd;
chrpath_cmd << chrpath_path << " -c -r \"" << rpath << "\" " << deploy_path;
exec(chrpath_cmd.str().c_str());
}

std::stringstream strip_cmd;
strip_cmd << strip << ' ' << deploy_path;
exec(strip_cmd.str().c_str());
if (strip) {
std::stringstream strip_cmd;
strip_cmd << strip_path << ' ' << deploy_path;
exec(strip_cmd.str().c_str());
}
}
}
Loading

0 comments on commit b119da5

Please sign in to comment.