Skip to content

Commit

Permalink
Merge pull request #199 from JonathonReinhart/more-startup-info
Browse files Browse the repository at this point in the history
Log more startup info
  • Loading branch information
JonathonReinhart authored Oct 9, 2021
2 parents 751ad8d + 86e1011 commit 922b32c
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 29 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- Log additional diagnostic information at startup ([#199])


## [0.13.1] - 2021-10-06
### Added
- Log staticx version and arguments at startup ([#197])
Expand Down Expand Up @@ -262,3 +267,4 @@ Initial release
[#185]: https://github.com/JonathonReinhart/staticx/pull/185
[#192]: https://github.com/JonathonReinhart/staticx/pull/192
[#197]: https://github.com/JonathonReinhart/staticx/pull/197
[#199]: https://github.com/JonathonReinhart/staticx/pull/199
9 changes: 9 additions & 0 deletions bootloader/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@ import sys

Import('env')

def cppdef_stringify(s):
"""Add quotes to a string suitable for a C preprocessor -D argument"""
s = '"' + s + '"' # Add " for the C string literal
s = "'" + s + "'" # Add ' for the shell
return s

env.Append(
CCFLAGS = ['-static'],
LINKFLAGS = ['-static'],
CPPPATH = [
'#libtar',
'#libxz',
],
CPPDEFINES = {
'COMPILER_PATH': cppdef_stringify(env['CC']),
}
)

bootloader = env.Program(
Expand Down
14 changes: 12 additions & 2 deletions bootloader/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -408,11 +408,21 @@ get_real_prog_path(void)
return result;
}

static void identify(void)
{
debug_printf("bootloader version %s\n", STATICX_VERSION);
debug_printf("compiled %s at %s by %s version %s\n",
__DATE__, __TIME__, COMPILER_PATH, __VERSION__);

/* If we're invoked by staticx, just exit */
if (getenv("STATICX_BOOTLOADER_IDENTIFY"))
exit(0);
}

int
main(int argc, char **argv)
{
debug_printf("Version %s\n", STATICX_VERSION);

identify();
xz_crc32_init();

/* Create temporary directory where archive will be extracted */
Expand Down
49 changes: 30 additions & 19 deletions staticx/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
from os.path import basename, islink
import logging
import subprocess

from .errors import *
from .utils import *
Expand All @@ -18,22 +19,6 @@
from .version import __version__



def _get_bootloader(debug=False):
"""Get a temporary copy of the bootloader"""
fbl = copy_asset_to_tempfile('bootloader', debug, prefix='staticx-output-', delete=False)
make_executable(fbl.name)
return fbl.name


def _check_bootloader_compat(bootloader, prog):
"""Verify the bootloader machine matches that of the user program"""
bldr_mach = get_machine(bootloader)
prog_mach = get_machine(prog)
if bldr_mach != prog_mach:
raise FormatMismatchError("Bootloader machine ({}) doesn't match "
"program machine ({})".format(bldr_mach, prog_mach))

class StaticxGenerator:
"""StaticxGenerator is responsible for producing a staticx-ified executable.
"""
Expand All @@ -53,6 +38,7 @@ def __init__(self, prog, strip=False, compress=True, debug=False, cleanup=True):
self._generate_called = False
self._added_libs = {}

# Temporary output file (bootloader copy)
self.tmpoutput = None
self.tmpprog = None
self.tmpdir = None
Expand Down Expand Up @@ -86,6 +72,32 @@ def _cleanup(self):
self.sxar = None


def _get_bootloader(self):
# Get a temporary copy of the bootloader
fbl = copy_asset_to_tempfile('bootloader', self.debug,
prefix='staticx-output-', delete=False)
with fbl:
self.tmpoutput = bootloader = fbl.name
make_executable(bootloader)

# Verify the bootloader machine matches that of the user program
bldr_mach = get_machine(bootloader)
prog_mach = get_machine(self.orig_prog)
if bldr_mach != prog_mach:
raise FormatMismatchError("Bootloader machine ({}) doesn't match "
"program machine ({})".format(bldr_mach, prog_mach))

# Run the bootloader for identification
r = subprocess.run(
args = [bootloader],
env = dict(STATICX_BOOTLOADER_IDENTIFY='1'),
stderr = subprocess.PIPE,
universal_newlines = True, # TODO: 'text' in Python 3.7
)
r.check_returncode()
lines = (line.split(':', 1)[1].strip() for line in r.stderr.splitlines())
logging.debug("Bootloader: " + " ".join(lines))


def generate(self, output):
"""Generate a Staticx program
Expand All @@ -100,9 +112,7 @@ def generate(self, output):
self._generate_called = True

# Work on a temp copy of the bootloader which becomes the output program
self.tmpoutput = _get_bootloader(self.debug)

_check_bootloader_compat(self.tmpoutput, self.orig_prog)
self._get_bootloader()

# First, learn things about the original program
orig_interp = get_prog_interp(self.orig_prog)
Expand Down Expand Up @@ -289,6 +299,7 @@ def generate(prog, output, libs=None, strip=False, compress=True, debug=False):
"""

logging.info("Running StaticX version {}".format(__version__))
verify_tools()
logging.debug("Arguments:")
logging.debug(" prog: {!r}".format(prog))
logging.debug(" output: {!r}".format(output))
Expand Down
43 changes: 35 additions & 8 deletions staticx/elf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,21 @@
import os
from pprint import pformat

import elftools
from elftools.elf.elffile import ELFFile
from elftools.elf.dynamic import DynamicSegment
from elftools.common.exceptions import ELFError

from .errors import *
from .utils import coerce_sequence, single
from .utils import coerce_sequence, single, which_exec


def verify_tools():
logging.info("Libraries:")
logging.info(" elftools: {}".format(elftools.__version__))

extern_tools_verify()


class ExternTool:
def __init__(self, cmd, os_pkg, stderr_ignore=[], encoding=None):
Expand All @@ -30,11 +39,12 @@ def __should_ignore(self, line):
return True
return False

def run(self, *args, **kw):
def run(self, *args, _internal=False, **kw):
args = list(args)
args.insert(0, self.cmd)

logging.debug("Running " + str(args))
if not _internal:
logging.debug("Running " + str(args))
try:
r = subprocess.run(
args = args,
Expand All @@ -52,10 +62,11 @@ def run(self, *args, **kw):
r.stderr = r.stderr.decode(self.encoding)

# Hide ignored lines from stderr
for line in r.stderr.splitlines(True):
if self.__should_ignore(line):
continue
sys.stderr.write(line)
if not _internal:
for line in r.stderr.splitlines(True):
if self.__should_ignore(line):
continue
sys.stderr.write(line)

return r.returncode, r.stdout

Expand All @@ -68,10 +79,18 @@ def run_check(self, *args, **kw):

return stdout

def get_version(self):
rc, output = self.run('--version', _internal=True)
if rc == 0:
return output.splitlines()[0]
return "??? (exited {})".format(rc)

def which(self):
return which_exec(self.cmd)


tool_ldd = ExternTool(os.getenv("STATICX_LDD", "ldd"), 'binutils')

tool_ldd = ExternTool(os.getenv("STATICX_LDD", "ldd"), 'libc-bin')
tool_objcopy = ExternTool('objcopy', 'binutils')
tool_patchelf = ExternTool('patchelf', 'patchelf',
stderr_ignore = [
Expand All @@ -82,6 +101,14 @@ def run_check(self, *args, **kw):
)
tool_strip = ExternTool('strip', 'binutils')

all_tools = (tool_ldd, tool_objcopy, tool_strip, tool_patchelf)

def extern_tools_verify():
logging.debug("External tools:")
for t in all_tools:
logging.info(" {}: {}: {}".format(t.cmd, t.which(), t.get_version()))


class LddError(ToolError):
def __init__(self, message):
super().__init__('ldd', message)
Expand Down
9 changes: 9 additions & 0 deletions staticx/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ def copy_fileobj_to_tempfile(fsrc, **kwargs):
fdst.seek(0)
return fdst


def which_exec(name, env=None):
for path in os.get_exec_path(env=env):
xp = os.path.join(path, name)
if os.access(xp, os.X_OK):
return xp
return None


def is_iterable(x):
"""Returns true if x is iterable but not a string"""
return isinstance(x, Iterable) and not isinstance(x, str)
Expand Down
12 changes: 12 additions & 0 deletions unittest/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import tempfile
import os
import pytest
import subprocess

from staticx import utils

Expand Down Expand Up @@ -63,3 +64,14 @@ def test_single_empty_default():

def test_single_key_none_default():
assert utils.single([1, 2, 3], key=lambda x: x<0, default='ok') == 'ok'

# which_exec
def test_which_exec_common():
def ext_which(name):
return subprocess.check_output(['which', name]).decode().strip()

for name in ('true', 'date', 'bash', 'python3'):
assert ext_which(name) == utils.which_exec(name)

def test_which_exec_bogus():
assert utils.which_exec('zZzZzZzZzZzZzZz') == None

0 comments on commit 922b32c

Please sign in to comment.